<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
            <title type="text">fx67ll's blog</title>
            <subtitle type="text">  Hi！欢迎访问 fx67ll.com 的个人博客站点！Code is my life ~</subtitle>
    <updated>2026-09-04T15:32:03+08:00</updated>
        <id>https://fx67ll.xyz</id>
        <link rel="alternate" type="text/html" href="https://fx67ll.xyz" />
        <link rel="self" type="application/atom+xml" href="https://fx67ll.xyz/atom.xml" />
    <rights>Copyright © 2026, fx67ll's blog</rights>
    <generator uri="https://halo.run/" version="1.5.2">Halo</generator>
            <entry>
                <title><![CDATA[2026 减重实践记录（瓜版）]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2026-fit-weight-nn" />
                <id>tag:https://fx67ll.xyz,2026-08-29:2026-fit-weight-nn</id>
                <published>2026-08-29T14:30:44+08:00</published>
                <updated>2026-09-03T11:31:33+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%F0%9F%94%A5-163.5cm%2F122%E6%96%A4%E5%8A%A0%E7%8F%AD%E5%A5%B3%E5%AD%A9%E5%B9%B4%E6%9C%AB%E5%87%8F%E8%84%82%E5%AE%9E%E6%88%98%E5%8D%9A%E6%96%87%EF%BC%9A%E4%B8%8D%E9%A5%BF%E6%99%95%E3%80%81%E4%B8%8D%E7%96%AF%E7%8B%82%E8%B7%B3%E6%93%8D%EF%BC%8C2026.12.31%E7%A8%B3%E6%8B%BF100%E6%96%A4-%F0%9F%92%AA" tabindex="-1">🔥 163.5cm/122斤加班女孩年末减脂实战博文：不饿晕、不疯狂跳操，2026.12.31稳拿100斤 💪</h1><h2 id="%F0%9F%93%85-%E5%8F%91%E5%B8%83%E6%97%A5%E6%9C%9F%EF%BC%9A2026%E5%B9%B48%E6%9C%8829%E6%97%A5%E2%8F%B3-%E6%89%A7%E8%A1%8C%E5%91%A8%E6%9C%9F%EF%BC%9A2026%E5%B9%B48%E6%9C%8831%E6%97%A5%EF%BC%88%E5%91%A8%E4%B8%80%EF%BC%89-%E2%80%94%E2%80%94-2026%E5%B9%B412%E6%9C%8831%E6%97%A5%EF%BC%88%E5%85%B1123%E5%A4%A9%EF%BC%8C%E7%BA%A617.6%E5%91%A8%EF%BC%89%F0%9F%8E%AF-%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF%EF%BC%9A%E5%B7%A5%E4%BD%9C%E6%97%A5%EF%BC%88%E5%91%A8%E4%B8%80%E3%80%81%E4%BA%8C%E3%80%81%E5%9B%9B%EF%BC%89%E5%8A%A0%E7%8F%AD%E8%87%B320%3A30%EF%BC%8C%E5%88%B0%E5%AE%B621%3A30-22%3A00%EF%BC%9B%E5%91%A8%E6%9C%AB%E6%97%B6%E9%97%B4%E8%87%AA%E7%94%B1%E3%80%82" tabindex="-1"><strong>📅 发布日期</strong>：2026年8月29日<br /><strong>⏳ 执行周期</strong>：2026年8月31日（周一） —— 2026年12月31日（共123天，约17.6周）<br /><strong>🎯 适用场景</strong>：工作日（周一、二、四）加班至20:30，到家21:30-22:00；周末时间自由。</h2><h2 id="%F0%9F%93%8C-%E5%86%99%E5%9C%A8%E5%89%8D%E9%9D%A2%EF%BC%9A%E5%A5%B3%E7%94%9F%E5%87%8F%E8%84%82%E5%BF%85%E9%A1%BB%E5%85%88%E6%83%B3%E6%98%8E%E7%99%BD%E7%9A%84%E4%B8%89%E4%BB%B6%E4%BA%8B-%F0%9F%A7%A0" tabindex="-1">📌 写在前面：女生减脂必须先想明白的三件事 🧠</h2><p>作为一个身高163.5cm、体重在60~62kg徘徊的&quot;微胖但藏肉型&quot;女孩 🧍‍♀️，我必须先承认三个事实：</p><ol><li><p><strong>🌙 晚上9点半以后跳帕梅拉/刘畊宏是自杀式减脂</strong> ⚠️。这个点拉高心率，皮质醇暴涨，不仅失眠 😵，还会掉肌肉、脸垮 🫠，第二天食欲翻倍想吃奶茶蛋糕 🧋。所以，<strong>工作日晚上不做有氧、不做高强度HIIT</strong> 🚫，但可以做15分钟低强度力量或拉伸（后面会讲）。</p></li><li><p><strong>🍽️ 饿肚子对女生伤害更大</strong> ❌。节食到停经、脱发、脸色蜡黄 🧟‍♀️，一旦恢复饮食立马反弹 📈。我必须靠<strong>调整吃饭时间</strong> ⏰ 和<strong>吃对高体积低热量食物</strong> 🥗 来制造缺口，而不是靠&quot;今天只吃一个苹果&quot;。</p></li><li><p><strong>💪 女生减脂必须保住肌肉和姨妈</strong> ⚠️。蛋白质吃不够、热量压太低，最先掉的是胸和屁股 🍑，然后是姨妈出走。好消息是163.5cm的100斤对应BMI≈18.7，刚好在健康区间内，不需要极端节食。本计划底线：<strong>每日热量不低于1350大卡，蛋白质不低于75g</strong> 🚫。</p></li></ol><p>基于这三点，我制定了这套 <strong>&quot;加班女孩友好型&quot;年末冲关方案</strong> 👩‍💼。目标不贪心：<strong>2026年12月31日，站上体重秤看到50<sub>51kg（100</sub>102斤）</strong> ⚖️（BMI≈18.7~19.1，健康偏瘦区间 ✨），余下塑形留给2027年春天 🌸。</p><blockquote><p>💡 <strong>容错声明</strong> 🤝：若受工作、身体状态影响，年末落在 <strong>51<sub>53kg（102</sub>106斤）</strong> 同样视为成功 ✅——姨妈正常、气色好、习惯存续，优先级高于体重数字。女生减脂拼的不是意志力，是<strong>策略和身体信号的解读能力</strong> 🧩。</p></blockquote><hr /><h2 id="%F0%9F%93%8A-%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86%EF%BC%9A%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE%E9%94%9A%E7%82%B9%EF%BC%88%E6%AF%8F%E5%91%A8%E4%B8%80%E6%97%A9%E4%B8%8A%E7%A9%BA%E8%85%B9%E7%A7%B0%E9%87%8D%EF%BC%89%E2%9A%96%EF%B8%8F" tabindex="-1">📊 第一部分：核心数据锚点（每周一早上空腹称重）⚖️</h2><table><thead><tr><th style="text-align:left">项目</th><th style="text-align:left">数据</th></tr></thead><tbody><tr><td style="text-align:left">🧍 身高</td><td style="text-align:left"><strong>163.5 cm</strong></td></tr><tr><td style="text-align:left">🧍 当前体重（取中值）</td><td style="text-align:left"><strong>61.0 kg（122斤）</strong></td></tr><tr><td style="text-align:left">🎯 年末目标体重</td><td style="text-align:left"><strong>50 ~ 51 kg（100~102斤）</strong></td></tr><tr><td style="text-align:left">📉 需减掉总量</td><td style="text-align:left"><strong>约 10 ~ 11 kg（20~22斤）</strong></td></tr><tr><td style="text-align:left">📅 每周安全减重速度</td><td style="text-align:left"><strong>0.5 ~ 0.65 kg / 周</strong></td></tr><tr><td style="text-align:left">🔥 每日热量摄入目标</td><td style="text-align:left"><strong>1400 ~ 1550 大卡</strong>（运动日取上限，休息日减100，不低于1350）</td></tr><tr><td style="text-align:left">💪 每日蛋白质目标</td><td style="text-align:left"><strong>80 ~ 90g</strong>（体重×1.3~1.5，保肌肉、防胸垮、稳姨妈）</td></tr><tr><td style="text-align:left">🍚 每日碳水目标</td><td style="text-align:left"><strong>150 ~ 180g</strong>（运动日吃够，保证加班脑力输出+姨妈正常 🧠）</td></tr><tr><td style="text-align:left">🥑 每日脂肪目标</td><td style="text-align:left"><strong>35 ~ 45g</strong>（优质脂肪不能砍太低，影响激素和皮肤 ✨）</td></tr><tr><td style="text-align:left">💧 每日饮水量</td><td style="text-align:left"><strong>1800 ~ 2200ml</strong></td></tr><tr><td style="text-align:left">🥬 每日蔬菜量</td><td style="text-align:left"><strong>≥ 400g</strong>（用体积换热量，填充胃部 🥗）</td></tr><tr><td style="text-align:left">🍫 每日铁/钙关注</td><td style="text-align:left">红肉/动物肝脏每周1~2次，牛奶/酸奶每天有 🥩🥛</td></tr></tbody></table><blockquote><p>📌 <strong>称重规则</strong> 📏：每周称重只记录<strong>周一早上空腹、上完厕所后</strong>的数值 🚽。单日涨跌无视 🙈，只看连续三周的下降趋势 📉。女生生理期前1周体重上涨1~2kg是水分潴留 💧，无需焦虑 😌，生理期结束后会自然回落。</p></blockquote><hr /><h2 id="%F0%9F%97%93%EF%B8%8F-%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86%EF%BC%9A%E4%B8%89%E9%98%B6%E6%AE%B5%E9%87%8C%E7%A8%8B%E7%A2%91%EF%BC%8818%E5%91%A8%E6%8B%86%E8%A7%A3%EF%BC%8C%E4%B8%AD%E6%9C%9F%E4%B8%8D%E8%BF%B7%E8%8C%AB%EF%BC%89%F0%9F%8E%AF" tabindex="-1">🗓️ 第二部分：三阶段里程碑（18周拆解，中期不迷茫）🎯</h2><table><thead><tr><th style="text-align:left">阶段</th><th style="text-align:left">时间</th><th style="text-align:left">目标体重</th><th style="text-align:left">体感标志</th></tr></thead><tbody><tr><td style="text-align:left">🏁 <strong>第一阶段·破冰期</strong></td><td style="text-align:left">8-9月（第1-7周）</td><td style="text-align:left">61 → 56.5~57.5kg</td><td style="text-align:left">🚶 走路同等距离心率回落，午后犯困减少，裤子腰围松一指 ✂️</td></tr><tr><td style="text-align:left">🏁 <strong>第二阶段·攻坚期</strong></td><td style="text-align:left">10-11月（第8-13周）</td><td style="text-align:left">56.5~57.5 → 52.5~53.5kg</td><td style="text-align:left">🦵 下肢线条变紧致，爬楼不喘，脸部水肿消退，姨妈周期稳定 ⚡</td></tr><tr><td style="text-align:left">🏁 <strong>第三阶段·收尾期</strong></td><td style="text-align:left">12月（第14-18周）</td><td style="text-align:left">52.5~53.5 → <strong>50~51kg</strong></td><td style="text-align:left">🔄 饮食运动模式完全内化，BMI进入健康偏瘦区间，具备长期维持能力 🏆</td></tr></tbody></table><hr /><h2 id="%E2%8F%B0-%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86%EF%BC%9A%E5%B7%A5%E4%BD%9C%E6%97%A5%EF%BC%88%E5%91%A8%E4%B8%80%E3%80%81%E4%BA%8C%E3%80%81%E5%9B%9B%EF%BC%89%E7%9A%84%22%E5%88%86%E9%92%9F%E7%BA%A7%E6%97%B6%E9%97%B4%E8%BD%B4%22%F0%9F%93%8B" tabindex="-1">⏰ 第三部分：工作日（周一、二、四）的&quot;分钟级时间轴&quot;📋</h2><p>这三天晚上被锁死 🔒，所以我的策略是：<strong>把晚餐拆成&quot;预晚餐（17:30）&quot; + “安抚餐（22:00）”</strong> 🍱，白天偷时间走路 🚶，到家做15分钟低强度力量或拉伸 🧘。</p><table><thead><tr><th style="text-align:left">时间点</th><th style="text-align:left">动作</th><th style="text-align:left">详细说明</th></tr></thead><tbody><tr><td style="text-align:left"><strong>🌅 07:30</strong></td><td style="text-align:left">🍳 早餐</td><td style="text-align:left">即食燕麦片 <strong>40g</strong> 🥣 + 纯牛奶 <strong>200ml</strong> 🥛 + 水煮全蛋 <strong>1个</strong> 🥚 + 小番茄 <strong>5颗</strong> 🍅。早上必须吃够蛋白，否则上午会馋甜食 🧁🚫。</td></tr><tr><td style="text-align:left"><strong>🕙 10:00</strong></td><td style="text-align:left">🥄 上午加餐</td><td style="text-align:left">无糖酸奶 <strong>100g</strong> 或 原味坚果 <strong>8g</strong> 🥜，应对上午工作消耗 💼。</td></tr><tr><td style="text-align:left"><strong>🕛 12:00</strong></td><td style="text-align:left">🍱 午餐（公司食堂/外卖）</td><td style="text-align:left">杂粮饭 <strong>100~120g熟重</strong> 🍚，肉类选去皮鸡腿/瘦牛肉/清蒸鱼/虾仁 <strong>120g</strong> 🍗🥩🐟🦐，绿叶蔬菜吃到饱（至少 <strong>200g</strong>）🥬。油大的菜用开水涮一遍再吃 💧。</td></tr><tr><td style="text-align:left"><strong>🕐 13:00 - 13:15</strong></td><td style="text-align:left">🚶 <strong>午间碎片化步行</strong> ⭐</td><td style="text-align:left">吃完午饭不趴桌 🙅，下楼快走 <strong>15分钟</strong>（约1.0<sub>1.2公里）📏。这15分钟能消耗约70</sub>80大卡 🔥，纯属白捡 🎁，还能防下午犯困。</td></tr><tr><td style="text-align:left"><strong>🕓 16:00</strong></td><td style="text-align:left">🍎 下午加餐（防爆饿）🛡️</td><td style="text-align:left">无糖酸奶 <strong>80g</strong> 🥛 + 半个苹果/几颗草莓 🍓。目的是撑到17:30的预晚餐，避免下班前抓狂点奶茶 🧋🚫。</td></tr><tr><td style="text-align:left"><strong>🕠 17:30 - 18:00</strong></td><td style="text-align:left">🍽️ <strong>“预晚餐”（重中之重）</strong> ⭐⭐⭐</td><td style="text-align:left">在办公室解决 💻。内容：<strong>蒸红薯/玉米 100g</strong> 🍠🌽 + 即食鸡胸肉/卤牛肉/虾仁 <strong>120g</strong> 🍗🥩🦐 + 自带黄瓜/圣女果 <strong>150g</strong> 🥒🍅。这一顿必须吃扎实，因为这是今天的&quot;正餐&quot; 🍽️。</td></tr><tr><td style="text-align:left"><strong>🕣 20:30</strong></td><td style="text-align:left">🏃 下班打卡</td><td style="text-align:left">路上不再吃东西 🚫🍔，只喝温水 💧。</td></tr><tr><td style="text-align:left"><strong>🕙 22:00</strong></td><td style="text-align:left">🏠 到家 &amp; <strong>“安抚餐”</strong> 😴</td><td style="text-align:left">如果饿得睡不着，只允许吃 <strong>无糖热牛奶 150ml</strong> 🥛 + <strong>1个鸡蛋白</strong> 🥚。如果只是微微饿，直接喝一杯温水就去洗漱 🚿。<strong>严禁</strong>吃面条、饼干、蛋糕、水果 🍜🍪🍰🍎🚫（睡前果糖=囤脂肪+第二天脸肿）。</td></tr><tr><td style="text-align:left"><strong>🕙 22:15 - 22:30</strong></td><td style="text-align:left">🧘 <strong>15分钟低强度力量/拉伸</strong> ⭐</td><td style="text-align:left">臀桥3×15 + 靠墙静蹲3×30秒 + 站姿提踵3×15 + 全身拉伸 🧘。<strong>不做跳跃、不做平板支撑超过20秒</strong> 🚫，避免神经亢奋影响入睡 😴。（周二可仅做拉伸，周一、四执行）</td></tr><tr><td style="text-align:left"><strong>🕦 23:15</strong></td><td style="text-align:left">🌙 <strong>强制熄灯睡觉</strong> ⭐⭐⭐</td><td style="text-align:left">手机放客厅 📱🚫。保证至少 <strong>7.5小时</strong>睡眠 😴。睡眠不足会直接拉高第二天饥饿素+皮质醇，脸肿+馋甜食 🧋，这一条比运动还重要 ⚠️。</td></tr></tbody></table><hr /><h2 id="%F0%9F%8F%8B%EF%B8%8F-%E7%AC%AC%E5%9B%9B%E9%83%A8%E5%88%86%EF%BC%9A%E4%BC%91%E6%81%AF%E6%97%A5%EF%BC%88%E5%91%A8%E4%B8%89%E3%80%81%E4%BA%94%E3%80%81%E5%85%AD%E3%80%81%E6%97%A5%EF%BC%89%E7%9A%84%E8%BF%90%E5%8A%A8%E6%8E%92%E6%9C%9F%E8%A1%A8-%F0%9F%93%85" tabindex="-1">🏋️ 第四部分：休息日（周三、五、六、日）的运动排期表 📅</h2><p>一周练 <strong>4天</strong> 🗓️，分布在不用加班的晚上和周末白天。周三和周五是&quot;有氧日&quot; 🏃，周六是&quot;力量+步行日&quot; 💪🚶，周日是&quot;放松恢复日&quot; 🧘。</p><blockquote><p>⚠️ <strong>每次训练后必须做5-8分钟拉伸</strong> 🧘‍♀️，重点放松小腿、大腿前侧/后侧、臀部、肩颈 🦵，每个动作维持25秒 ⏱️。女生不拉伸，第二天腿酸+体态更差 😭。</p></blockquote><h3 id="%F0%9F%94%B9-%E5%91%A8%E4%B8%89%EF%BC%88%E6%AD%A3%E5%B8%B8%E4%B8%8B%E7%8F%AD%EF%BC%89%EF%BC%9A%F0%9F%9A%B6-%E6%88%B7%E5%A4%96%E5%BF%AB%E8%B5%B0-%E6%88%96-%F0%9F%8F%8A-%E6%B8%B8%E6%B3%B3-%E4%BA%8C%E9%80%89%E4%B8%80" tabindex="-1">🔹 周三（正常下班）：🚶 户外快走 或 🏊 游泳 二选一</h3><ul><li><strong>🚶 选项A（户外快走）</strong>：总时长 <strong>45分钟</strong> ⏱️，约4~4.5公里 📏。前5分钟慢走热身 🟢，中间30分钟维持心率 <strong>135~150</strong> ❤️（体感：微喘但能说出完整句子 🗣️），最后10分钟慢走冷身 🔵。优先平坦路线，穿软底鞋 👟，保护膝盖 🛡️。</li><li><strong>🏊 选项B（游泳）</strong>：总时长 <strong>35分钟</strong> ⏱️。8分钟慢游热身 🟢 + 22分钟间歇循环（游50m，漂浮休息30s）🔄 + 5分钟放松 😌。心率控制125~145 ❤️。游泳后<strong>必须</strong>在30分钟内吃1个水煮蛋或喝半杯牛奶 🥚💪，否则晚上会饿到暴食 🍔⚠️。</li></ul><h3 id="%F0%9F%94%B9-%E5%91%A8%E4%BA%94%EF%BC%88%E6%AD%A3%E5%B8%B8%E4%B8%8B%E7%8F%AD%EF%BC%89%EF%BC%9A%F0%9F%9A%B4-%E5%9B%BA%E5%AE%9A%E9%98%BB%E5%B0%BC%E5%8D%95%E8%BD%A6%C2%B7%E5%8F%98%E9%80%9F%E9%AA%91%EF%BC%8835%E5%88%86%E9%92%9F%EF%BC%89%E2%AD%90" tabindex="-1">🔹 周五（正常下班）：🚴 固定阻尼单车·变速骑（35分钟）⭐</h3><p>家里的单车靠 <strong>“踏频”</strong> 和 <strong>“站姿/坐姿”</strong> 模拟强度变化 🎚️：</p><table><thead><tr><th style="text-align:left">时间段</th><th style="text-align:left">动作模式</th><th style="text-align:left">踏频目标</th><th style="text-align:left">心率目标</th><th style="text-align:left">作用</th></tr></thead><tbody><tr><td style="text-align:left">🟢 0~5分钟</td><td style="text-align:left">坐姿，匀速轻踩</td><td style="text-align:left">55~60 rpm</td><td style="text-align:left">100~115</td><td style="text-align:left">唤醒膝关节 💧</td></tr><tr><td style="text-align:left">🔥 6~18分钟</td><td style="text-align:left">坐姿，加速高频踩</td><td style="text-align:left"><strong>75~80 rpm</strong> ⭐</td><td style="text-align:left">135~150</td><td style="text-align:left">黄金燃脂主区间 💪</td></tr><tr><td style="text-align:left">🔵 19~21分钟</td><td style="text-align:left">坐姿，大幅降速缓踩</td><td style="text-align:left">55 rpm</td><td style="text-align:left">≤125</td><td style="text-align:left">短暂缓冲，排乳酸 😵</td></tr><tr><td style="text-align:left">⚡ 22~30分钟</td><td style="text-align:left"><strong>站姿慢踩 + 坐姿高频 交替</strong>（每2分钟切换）🔄</td><td style="text-align:left">站姿45 rpm / 坐姿75 rpm</td><td style="text-align:left">140~155</td><td style="text-align:left">站姿模拟爬坡 ⛰️，强化臀腿 🦵</td></tr><tr><td style="text-align:left">🧊 31~35分钟</td><td style="text-align:left">坐姿，逐渐减速至停止</td><td style="text-align:left">55 → 35 rpm</td><td style="text-align:left">降至≤115</td><td style="text-align:left">冷身，防第二天腿疼 🦵✅</td></tr></tbody></table><blockquote><p>🚫 <strong>全程禁止站立猛踩</strong> ⚠️，固定阻尼单车站姿发力对膝关节压力极大 🦵💥，女生核心力量偏弱更要保护膝盖 🛡️。</p></blockquote><h3 id="%F0%9F%94%B9-%E5%91%A8%E5%85%AD%EF%BC%88%E5%85%A8%E5%A4%A9%E8%87%AA%E7%94%B1%EF%BC%89%EF%BC%9A%F0%9F%92%AA-%E5%8A%9B%E9%87%8F%E8%AE%AD%E7%BB%83%EF%BC%8825%E5%88%86%E9%92%9F%EF%BC%89%2B-%F0%9F%9A%B6-%E9%95%BF%E8%B7%9D%E7%A6%BB%E6%AD%A5%E8%A1%8C%EF%BC%8850%E5%88%86%E9%92%9F%EF%BC%89%E2%AD%90%E2%AD%90" tabindex="-1">🔹 周六（全天自由）：💪 力量训练（25分钟）+ 🚶 长距离步行（50分钟）⭐⭐</h3><p><strong>顺序不能错</strong> ❗：先做力量（消耗糖原 🔋），休息10分钟后 ☕，再去长距离步行（直接燃烧脂肪 🔥）。</p><p><strong>💪 力量训练（居家自重，无需器械）</strong> 🏠：</p><ul><li>🍑 臀桥：4组 × 15次（顶峰收缩停2秒 ⏱️，强化臀大肌，改善假胯宽和塌屁股 🦵）</li><li>🦵 徒手深蹲：3组 × 12次（下蹲时膝盖对准脚尖 👀，别内扣）</li><li>🦵 侧卧抬腿：3组 × 15次 / 每条腿（改善大腿外侧赘肉，练臀中肌 ⚖️）</li><li>🦶 站姿提踵：3组 × 18次（瘦小腿、缓解水肿 🦵）</li><li>🧱 跪姿平板支撑：3组 × 25秒（核心收紧，改善小腹突出 💪）</li></ul><p>组间休息60秒 ⏱️，总耗时严格控制在25分钟内 ⌛。</p><p><strong>🚶 长距离步行</strong>：<br />换双软底鞋 👟，出门快走 <strong>50分钟</strong>（约4~4.5公里）📏。心率维持在 <strong>115~130</strong> ❤️（体感：能边走边哼歌 🎵）。这一步是&quot;脂肪燃烧的扫尾工作&quot; 🧹。</p><blockquote><p>🍰 <strong>周六中午设为每周放松餐</strong> 🎉⭐：可以吃想吃的东西——火锅、奶茶、蛋糕都行 🧋🍰，不计算热量 📊🚫。但仅限一餐，不是一整天放开 🙅。吃完后当天晚餐恢复正常饮食 🍽️。放松餐是防止长期压抑导致报复性暴食的减压阀 😋⚠️。</p></blockquote><h3 id="%F0%9F%94%B9-%E5%91%A8%E6%97%A5%EF%BC%88%E7%BA%AF%E6%94%BE%E6%9D%BE%EF%BC%89%EF%BC%9A%F0%9F%A7%98-%E6%81%A2%E5%A4%8D%E6%80%A7%E6%B8%B8%E6%B3%B3%2F%E6%95%A3%E6%AD%A5-%E6%88%96-%E5%BD%BB%E5%BA%95%E8%BA%BA%E5%B9%B3-%F0%9F%98%B4" tabindex="-1">🔹 周日（纯放松）：🧘 恢复性游泳/散步 或 彻底躺平 😴</h3><ul><li>如果本周感觉疲劳 😩，周日 <strong>只游泳25分钟</strong> 🏊，慢悠悠地游，不计时不计速 ⏱️🚫，纯粹放松肌肉 💆。</li><li>如果本周感觉精力尚可 ✨，周日改为 <strong>陪家人/男友遛弯60分钟</strong> 👨‍👩‍👧 或 <strong>在家做全身拉伸20分钟</strong> 🧘。</li><li><strong>🛡️ 核心原则</strong>：周日不安排任何高强度训练 🚫🔥，为下周一的加班储备体力 🔋。生理期前3天和生理期前2天，全部运动降级为散步或拉伸 🚫🏃。</li></ul><hr /><h2 id="%F0%9F%A5%97-%E7%AC%AC%E4%BA%94%E9%83%A8%E5%88%86%EF%BC%9A%E9%A5%AE%E9%A3%9F%22%E9%A5%B1%E5%92%8C%E6%84%9F%22%E5%AE%9E%E6%93%8D%E6%B8%85%E5%8D%95%EF%BC%88%E7%85%A7%E7%9D%80%E4%B9%B0%EF%BC%8C%E4%B8%8D%E8%B4%B9%E8%84%91%EF%BC%89%F0%9F%9B%92" tabindex="-1">🥗 第五部分：饮食&quot;饱和感&quot;实操清单（照着买，不费脑）🛒</h2><p>为了避免饿肚子 😋，冰箱和工位抽屉常备以下食物 🧊。记住一个替换法则：<strong>用体积换热量</strong> 📐。</p><table><thead><tr><th style="text-align:left">类别</th><th style="text-align:left">✅ 推荐食物（无限接近随便吃）</th><th style="text-align:left">⚠️ 限量吃（注意克重）</th><th style="text-align:left">🚫 禁止吃（一口都别碰）</th></tr></thead><tbody><tr><td style="text-align:left"><strong>🍚 主食</strong></td><td style="text-align:left">蒸南瓜、冬瓜、白萝卜、魔芋 🎃🥬</td><td style="text-align:left">燕麦40g、红薯/玉米100g、杂粮饭100~120g 🥣🍠🌽</td><td style="text-align:left">白米饭/面条/馒头/面包/年糕（除非午餐熟重100g以内）🍜🍞🚫</td></tr><tr><td style="text-align:left"><strong>💪 蛋白质</strong></td><td style="text-align:left">鸡蛋白、无糖酸奶、纯牛奶、豆腐 🥚🥛</td><td style="text-align:left">鸡胸肉、瘦牛肉、鱼虾、虾仁（每天共120g）🍗🥩🐟🦐</td><td style="text-align:left">肥牛卷、五花肉、炸鸡、腊肠、培根 🥓🍗🚫</td></tr><tr><td style="text-align:left"><strong>🥬 蔬菜</strong></td><td style="text-align:left"><strong>绿叶菜、黄瓜、番茄、西兰花、菌菇、海带</strong> 🥦🍄（每天至少400g，清炒/水煮/凉拌）</td><td style="text-align:left">土豆、莲藕、山药（算主食）🥔</td><td style="text-align:left">油炸蔬菜（地三鲜、干锅花菜）🍆🚫</td></tr><tr><td style="text-align:left"><strong>🥑 脂肪</strong></td><td style="text-align:left">橄榄油（喷两下）、坚果（每天8g）🫒🥜</td><td style="text-align:left">蛋黄（每天最多1个）🥚、牛油果（每周半个）</td><td style="text-align:left">动物油、黄油、人造奶油、奶茶奶盖 🧈🧋🚫</td></tr><tr><td style="text-align:left"><strong>🍫 零食替代</strong></td><td style="text-align:left">魔芋爽（低盐）、海苔、气泡水 🫧</td><td style="text-align:left">黑巧克力（70%以上，每天1小块）🍫</td><td style="text-align:left">薯片、饼干、蛋糕、奶茶、蜜饯 🍪🧋🚫</td></tr></tbody></table><p><strong>🍽️ 一个具体的执行日食谱示例（周六休息日 · 约1500kcal）📋</strong>：</p><ul><li><strong>🌅 早餐（08:00）</strong>：全麦面包1片 🍞 + 水煮蛋1个 🥚 + 黑咖啡/无糖豆浆1杯 ☕ + 小番茄5颗 🍅。</li><li><strong>🕛 午餐（12:00）</strong>：糙米饭100g 🍚 + 清蒸鲈鱼150g 🐟 + 蒜蓉西兰花250g 🥦（先吃菜和肉，最后吃米饭 🍚）。</li><li><strong>🕒 运动前加餐（15:00）</strong>：力量训练前1小时，吃半根香蕉 🍌（防低血糖头晕 😵）。</li><li><strong>🕡 晚餐（18:30）</strong>：蒸南瓜150g 🎃 + 卤牛肉100g 🥩 + 凉拌黄瓜海带丝250g 🥒。</li><li><strong>🕢 运动后补给（19:30）</strong>：乳清蛋白半勺 或 鸡蛋白1个 🥚💪，运动结束30~60分钟内完成 ⏱️。</li><li><strong>🌙 睡前（22:00）</strong>：如果不饿就跳过 🙈，如果饿就喝100ml热牛奶 🥛。</li></ul><blockquote><p>📌 <strong>休息日调整</strong> 📊：午、晚餐主食各减少15~20g 🍚，蛋白、蔬菜保持不变 💪🥬，不盲目砍饭。<strong>生理期前一周</strong>：碳水上调20~30g，允许每天多吃1小块黑巧克力 🍫，缓解情绪波动和 cravings。</p></blockquote><hr /><h2 id="%F0%9F%9A%A8-%E7%AC%AC%E5%85%AD%E9%83%A8%E5%88%86%EF%BC%9A%E5%BA%94%E6%80%A5%E8%B0%83%E6%95%B4%E4%B8%8E%E5%B9%B3%E5%8F%B0%E6%9C%9F%E7%A0%B4%E5%B1%80%E6%B3%95-%F0%9F%9B%A0%EF%B8%8F" tabindex="-1">🚨 第六部分：应急调整与平台期破局法 🛠️</h2><p>总会有加班到崩溃 😩、下雨没法出门 🌧️、体重连续两周不动、或者生理期乱掉的时刻 ⚖️。针对此，我设置了六条底线 🛡️：</p><ol><li><strong>😴 如果加班到晚上10点且极度疲惫</strong>：直接放弃&quot;安抚餐&quot;中的蛋白和15分钟力量 🚫💪，只喝温水就睡 💧。身体需要休息时，少摄入比多运动更重要 ⚠️。</li><li><strong>📉 如果体重卡在某个数值超过2周不动</strong>：不要减少主食 🍚🚫，优先排查油脂、零食、奶茶、水果摄入 🧐，<strong>每日削减100~150大卡（从脂肪和零食里砍，不动主食）</strong> 🔥。同时把周三的快走换成<strong>游泳间歇</strong> 🏊⚡（冲刺游25米+慢游25米交替，持续15分钟），刺激一下心肺 ❤️。</li><li><strong>🍔 如果某天暴食吃了一顿大餐（比如聚餐/火锅）</strong>：不催吐 🚫🤮、不疯狂加练 🚫🏃。第二天只做一件事——午餐和晚餐的<strong>主食全部换成冬瓜/南瓜/魔芋</strong> 🎃🥬，蛋白质和蔬菜翻倍 💪🥬，一天内把多余水分排出去 💧，体重就会回来 ⚖️。</li><li><strong>😋 如果持续饥饿、训练乏力、工作精神差、姨妈推迟</strong>：立刻上调碳水摄入量 🍚⬆️，运动日前一餐适度增加主食 🍞，热量缺口不可过大 ⚠️。<strong>姨妈比体重重要100倍</strong> 🚺——如果连续2个月姨妈异常，立刻停止减脂，恢复正常饮食并就医 🏥。</li><li><strong>🩸 生理期前3天+经期前2天</strong>：所有有氧降级为散步或拉伸 🚫🏃，力量训练减半。允许每天多吃100~150大卡（优先碳水和热饮）☕，身体在这个阶段本来就需要更多能量，硬扛只会适得其反。</li><li><strong>🌧️ 如果天气恶劣或身心极度疲惫</strong>：切换室内单车或原地踏步 🚴🚶，不强行外出 🌧️。习惯的延续优先于训练打卡 ✅。</li></ol><hr /><h2 id="%F0%9F%8F%86-%E7%AC%AC%E4%B8%83%E9%83%A8%E5%88%86%EF%BC%9A%E7%BB%99%E5%B9%B4%E5%BA%95%E8%87%AA%E5%B7%B1%E7%9A%84%E9%AA%8C%E6%94%B6%E6%A0%87%E5%87%86-%F0%9F%8E%AF" tabindex="-1">🏆 第七部分：给年底自己的验收标准 🎯</h2><p>2026年12月31日早上 🌅，我不需要看到马甲线 💪🚫，也不需要能跑半马 🏃🚫。我只需要确认以下 <strong>4个硬指标</strong> 达成即可宣告胜利 🎉：</p><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> ⚖️ <strong>体重秤读数稳定在 50.0 ~ 51.0 kg（100~102斤）之间</strong>（连续三天晨重，避开生理期）。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 🩸 <strong>姨妈周期稳定在28~35天，量正常</strong>（证明激素没被减脂搞乱 🚺）。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 🚶 <strong>爬6层楼梯不喘、次日不剧烈酸痛</strong>（证明心肺和臀腿力量提升 ⬆️）。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 📏 <strong>腰围（肚脐水平）缩减至少 6<sub>8cm，大腿围缩减3</sub>4cm</strong>（脂肪真的掉了，不是只掉水分 ❤️）。</li></ul><blockquote><p>✅ <strong>放宽标准</strong> 🤝：若年末体重落在51<sub>53kg（102</sub>106斤），但腰围缩减≥5cm、姨妈正常、气色变好、运动习惯已建立 🔄，同样算成功 🏆。数字只是结果 📊，健康、气色、精力提升才是更重要的收获 ✨。</p></blockquote><hr /><h2 id="%F0%9F%92%8C-%E5%86%99%E5%9C%A8%E6%9C%80%E5%90%8E%EF%BC%9A%E4%B8%8D%E8%BF%BD%E6%B1%82%E5%AE%8C%E7%BE%8E%EF%BC%8C%E5%8F%AA%E8%BF%BD%E6%B1%82%22%E5%8F%AF%E6%8C%81%E7%BB%AD%22-%F0%9F%94%84" tabindex="-1">💌 写在最后：不追求完美，只追求&quot;可持续&quot; 🔄</h2><p>这套方案最狠的地方不在于运动量 🏋️，而在于 <strong>“彻底放弃了工作日晚上必须跳操出汗的执念”</strong> 🧠✨。既然加班是无法改变的事实 💼，那就把睡眠、预晚餐和15分钟低强度力量做到极致 😴🍽️🧘。</p><p>到年底还有18周 🗓️，我不需要每天都做得满分 💯🚫，只要做到 <strong>80分</strong> ✅（比如每周漏掉1次游泳 🏊，或者有2天没控制住喝了半杯奶茶 🧋），依然能稳稳达成目标 🎯。每周1次放松餐不是破功 🍰，是为了让这套方案能跑满18周而设计的减压阀 🧘。</p><p>163.5cm的100斤，BMI≈18.7，刚好卡在健康区间内，<strong>再低就偏瘦了</strong> 🚫。这个身高配这个体重，视觉上会是纤细但不干瘪的状态，穿衣服好看也有气色。女生减脂最怕的不是慢，是<strong>快进快出、伤了身体又反弹</strong> 📈。慢慢来，比较快。</p><p>希望年底回看这篇博文时 📖，我能轻松地写下一句备注 ✍️：“目标达成 🎉，2027年春天准备向96斤+线条塑形发起快乐冲刺 🌸💪。”</p><p>共勉。💪✨</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2026 减重实践记录]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2026-fit-weight" />
                <id>tag:https://fx67ll.xyz,2026-08-29:2026-fit-weight</id>
                <published>2026-08-29T08:29:00+08:00</published>
                <updated>2026-08-31T09:37:10+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%F0%9F%94%A5-186cm%2F100kg-%E5%8A%A0%E7%8F%AD%E6%97%8F%E5%B9%B4%E6%9C%AB%E5%87%8F%E8%84%82%E5%AE%9E%E6%88%98%E5%8D%9A%E6%96%87%EF%BC%9A%E4%B8%8D%E9%A5%BF%E8%82%9A%E5%AD%90%E3%80%81%E4%B8%8D%E7%86%AC%E5%A4%9C%E8%BF%90%E5%8A%A8%EF%BC%8C2026.12.31%E7%A8%B3%E6%8B%BF85kg-%F0%9F%92%AA" tabindex="-1">🔥 186cm/100kg 加班族年末减脂实战博文：不饿肚子、不熬夜运动，2026.12.31稳拿85kg 💪</h1><p><strong>📅 发布日期</strong>：2026年8月29日<br /><strong>⏳ 执行周期</strong>：2026年8月31日（周一） —— 2026年12月31日（共123天，约17.6周）<br /><strong>🎯 适用场景</strong>：工作日（周一、二、四）加班至20:30，到家21:30-22:00；周末时间自由。</p><hr /><h2 id="%F0%9F%93%8C-%E5%86%99%E5%9C%A8%E5%89%8D%E9%9D%A2%EF%BC%9A%E8%AE%A4%E6%B8%85%E4%B8%A4%E4%B8%AA%E6%AE%8B%E9%85%B7%E4%BD%86%E7%9C%81%E4%BA%8B%E7%9A%84%E7%8E%B0%E5%AE%9E-%F0%9F%A7%A0" tabindex="-1">📌 写在前面：认清两个残酷但省事的现实 🧠</h2><p>作为一个身高186cm、体重在97~100kg晃荡的&quot;大骨架微胖男&quot; 🧍‍♂️，我必须先承认两个事实：</p><ol><li><strong>🌙 晚上9点半以后强行做有氧是自杀式减脂</strong> ⚠️。这个点拉高心率，皮质醇（压力激素）暴涨，不仅睡不着 😵，还会把肌肉当燃料烧掉 🔥，第二天食欲大开 🍔。所以，<strong>工作日晚上不做有氧、不做高强度训练</strong> 🚫，但可以做15分钟极简力量（后面会讲）。</li><li><strong>🍽️ 饿肚子对我无效</strong> ❌。饿到失眠只会降低基础代谢，一旦恢复饮食立马反弹 📈。我必须靠<strong>调整吃饭时间</strong> ⏰ 和<strong>吃对体积大的食物</strong> 🥗 来制造热量缺口。</li></ol><p>基于这两点，我制定了这套 <strong>&quot;加班狗友好型&quot;年末冲关方案</strong> 🐶💼。目标不贪心：<strong>2026年12月31日，站上体重秤看到85~87kg</strong> ⚖️（BMI≈24.6~25.2，踏入健康区间边缘 🎉），余下几公斤留给2027年春天慢慢收尾 🌸。</p><blockquote><p>💡 <strong>容错声明</strong> 🤝：若受工作、身体状态影响，年末落在 <strong>87~89kg</strong> 同样视为成功 ✅——习惯的存续优先级高于体重数字。大体重减脂拼的不是意志力，是<strong>策略和环境适应力</strong> 🧩。</p></blockquote><hr /><h2 id="%F0%9F%93%8A-%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86%EF%BC%9A%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE%E9%94%9A%E7%82%B9%EF%BC%88%E6%AF%8F%E5%91%A8%E4%B8%80%E6%97%A9%E4%B8%8A%E7%A9%BA%E8%85%B9%E7%A7%B0%E9%87%8D%EF%BC%89%E2%9A%96%EF%B8%8F" tabindex="-1">📊 第一部分：核心数据锚点（每周一早上空腹称重）⚖️</h2><table><thead><tr><th style="text-align:left">项目</th><th style="text-align:left">数据</th></tr></thead><tbody><tr><td style="text-align:left">🧍 当前体重（取中值）</td><td style="text-align:left"><strong>98.5 kg</strong></td></tr><tr><td style="text-align:left">🎯 年末目标体重</td><td style="text-align:left"><strong>85 ~ 87 kg</strong></td></tr><tr><td style="text-align:left">📉 需减掉总量</td><td style="text-align:left"><strong>约 11.5 ~ 13.5 kg</strong></td></tr><tr><td style="text-align:left">📅 每周安全减重速度</td><td style="text-align:left"><strong>0.6 ~ 0.8 kg / 周</strong></td></tr><tr><td style="text-align:left">🔥 每日热量摄入目标</td><td style="text-align:left"><strong>2000 ~ 2150 大卡</strong>（运动日取上限，休息日减150，不低于1700）</td></tr><tr><td style="text-align:left">💪 每日蛋白质目标</td><td style="text-align:left"><strong>120 ~ 140g</strong>（体重×1.3~1.4，保护肌肉不掉）</td></tr><tr><td style="text-align:left">🍚 每日碳水目标</td><td style="text-align:left"><strong>220 ~ 260g</strong>（运动日吃够，保证加班脑力输出 🧠）</td></tr><tr><td style="text-align:left">🥑 每日脂肪目标</td><td style="text-align:left"><strong>50 ~ 65g</strong>（优先优质脂肪，严控油炸 🚫🍟）</td></tr><tr><td style="text-align:left">💧 每日饮水量</td><td style="text-align:left"><strong>2200 ~ 2600ml</strong></td></tr><tr><td style="text-align:left">🥬 每日蔬菜量</td><td style="text-align:left"><strong>≥ 500g</strong>（用体积换热量，填充胃部 🥗）</td></tr></tbody></table><blockquote><p>📌 <strong>称重规则</strong> 📏：每周称重只记录<strong>周一早上空腹、上完厕所后</strong>的数值 🚽。单日涨跌无视 🙈，只看连续三周的下降趋势 📉。单日1~1.5kg的浮动大多是水分 💧，无需焦虑 😌。</p></blockquote><hr /><h2 id="%F0%9F%97%93%EF%B8%8F-%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86%EF%BC%9A%E4%B8%89%E9%98%B6%E6%AE%B5%E9%87%8C%E7%A8%8B%E7%A2%91%EF%BC%8818%E5%91%A8%E6%8B%86%E8%A7%A3%EF%BC%8C%E4%B8%AD%E6%9C%9F%E4%B8%8D%E8%BF%B7%E8%8C%AB%EF%BC%89%F0%9F%8E%AF" tabindex="-1">🗓️ 第二部分：三阶段里程碑（18周拆解，中期不迷茫）🎯</h2><table><thead><tr><th style="text-align:left">阶段</th><th style="text-align:left">时间</th><th style="text-align:left">目标体重</th><th style="text-align:left">体感标志</th></tr></thead><tbody><tr><td style="text-align:left">🏁 <strong>第一阶段·破冰期</strong></td><td style="text-align:left">8-9月（第1-7周）</td><td style="text-align:left">98.5 → 90~92kg</td><td style="text-align:left">🚴 骑行同等距离心率回落，日常疲惫感下降，跨过轻度肥胖门槛 ✂️</td></tr><tr><td style="text-align:left">🏁 <strong>第二阶段·攻坚期</strong></td><td style="text-align:left">10-11月（第8-13周）</td><td style="text-align:left">90~92 → 86~88kg</td><td style="text-align:left">🦵 下肢力量得到补强，爬楼后肌肉酸痛大幅缓解，有氧效率提升 ⚡</td></tr><tr><td style="text-align:left">🏁 <strong>第三阶段·收尾期</strong></td><td style="text-align:left">12月（第14-18周）</td><td style="text-align:left">86~88 → <strong>85~87kg</strong></td><td style="text-align:left">🔄 饮食运动模式完全内化，正式进入健康BMI区间，具备长期维持能力 🏆</td></tr></tbody></table><hr /><h2 id="%E2%8F%B0-%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86%EF%BC%9A%E5%B7%A5%E4%BD%9C%E6%97%A5%EF%BC%88%E5%91%A8%E4%B8%80%E3%80%81%E4%BA%8C%E3%80%81%E5%9B%9B%EF%BC%89%E7%9A%84%22%E5%88%86%E9%92%9F%E7%BA%A7%E6%97%B6%E9%97%B4%E8%BD%B4%22%F0%9F%93%8B" tabindex="-1">⏰ 第三部分：工作日（周一、二、四）的&quot;分钟级时间轴&quot;📋</h2><p>这三天晚上被锁死 🔒，所以我的策略是：<strong>把晚餐拆成&quot;预晚餐（17:30）&quot; + “安抚餐（22:00）”</strong> 🍱，白天偷时间走路 🚶，到家做15分钟极简力量 💪。</p><table><thead><tr><th style="text-align:left">时间点</th><th style="text-align:left">动作</th><th style="text-align:left">详细说明</th></tr></thead><tbody><tr><td style="text-align:left"><strong>🌅 07:30</strong></td><td style="text-align:left">🍳 早餐</td><td style="text-align:left">即食燕麦片 <strong>60g</strong> 🥣 + 纯牛奶 <strong>250ml</strong> 🥛 + 水煮全蛋 <strong>2个</strong> 🥚 + 小番茄 <strong>5颗</strong> 🍅。吃完出门 🚪。</td></tr><tr><td style="text-align:left"><strong>🕙 10:00</strong></td><td style="text-align:left">🥄 上午加餐</td><td style="text-align:left">无糖酸奶 <strong>150g</strong> 或 原味坚果 <strong>10g</strong> 🥜，应对上午工作消耗 💼。</td></tr><tr><td style="text-align:left"><strong>🕛 12:00</strong></td><td style="text-align:left">🍱 午餐（公司食堂/外卖）</td><td style="text-align:left">杂粮饭 <strong>130~150g熟重</strong> 🍚，肉类选去皮鸡腿/瘦牛肉/清蒸鱼 <strong>160g</strong> 🍗🥩🐟，绿叶蔬菜吃到饱（至少 <strong>250g</strong>）🥬。油大的菜用开水涮一遍再吃 💧。</td></tr><tr><td style="text-align:left"><strong>🕐 13:00 - 13:15</strong></td><td style="text-align:left">🚶 <strong>午间碎片化步行</strong> ⭐</td><td style="text-align:left">吃完午饭不趴桌 🙅，下楼快走 <strong>15分钟</strong>（约1.2公里）📏。这15分钟能消耗约100大卡 🔥，纯属白捡 🎁。</td></tr><tr><td style="text-align:left"><strong>🕓 16:00</strong></td><td style="text-align:left">🍎 下午加餐（防爆饿）🛡️</td><td style="text-align:left">无糖酸奶 <strong>100g</strong> 🥛 + 1个苹果/半个火龙果 🍎🐉。目的是撑到17:30的预晚餐，避免下班前抓狂吃饼干 🍪🚫。</td></tr><tr><td style="text-align:left"><strong>🕠 17:30 - 18:00</strong></td><td style="text-align:left">🍽️ <strong>“预晚餐”（重中之重）</strong> ⭐⭐⭐</td><td style="text-align:left">在办公室解决 💻。内容：<strong>蒸红薯/玉米 120g</strong> 🍠🌽 + 即食鸡胸肉/卤牛肉 <strong>150g</strong> 🍗🥩 + 自带黄瓜/圣女果 <strong>200g</strong> 🥒🍅。这一顿必须吃扎实，因为这是今天的&quot;正餐&quot; 🍽️。</td></tr><tr><td style="text-align:left"><strong>🕣 20:30</strong></td><td style="text-align:left">🏃 下班打卡</td><td style="text-align:left">路上不再吃东西 🚫🍔，只喝温水 💧。</td></tr><tr><td style="text-align:left"><strong>🕙 22:00</strong></td><td style="text-align:left">🏠 到家 &amp; <strong>“安抚餐”</strong> 😴</td><td style="text-align:left">如果饿得睡不着，只允许吃 <strong>无糖热牛奶 200ml</strong> 🥛 + <strong>2个鸡蛋白</strong>（扔掉蛋黄）🥚。如果只是微微饿，直接喝一杯温水就去洗漱 🚿。<strong>严禁</strong>吃面条、饼干、水果 🍜🍪🍎🚫（果糖会转化为肝糖储存）。</td></tr><tr><td style="text-align:left"><strong>🕙 22:15 - 22:30</strong></td><td style="text-align:left">💪 <strong>15分钟极简力量</strong> ⭐</td><td style="text-align:left">臀桥3×15 + 徒手深蹲3×12 + 站姿提踵3×15 + 简短拉伸 🧘。<strong>不做箭步蹲和平板支撑</strong> 🚫，避免神经亢奋影响入睡 😴。（周二可仅做拉伸，周一、四执行）</td></tr><tr><td style="text-align:left"><strong>🕦 23:15</strong></td><td style="text-align:left">🌙 <strong>强制熄灯睡觉</strong> ⭐⭐⭐</td><td style="text-align:left">手机放客厅 📱🚫。保证至少 <strong>7小时</strong>睡眠 😴。睡眠不足会直接拉高第二天的饥饿素 😋，这一条比运动还重要 ⚠️。</td></tr></tbody></table><hr /><h2 id="%F0%9F%8F%8B%EF%B8%8F-%E7%AC%AC%E5%9B%9B%E9%83%A8%E5%88%86%EF%BC%9A%E4%BC%91%E6%81%AF%E6%97%A5%EF%BC%88%E5%91%A8%E4%B8%89%E3%80%81%E4%BA%94%E3%80%81%E5%85%AD%E3%80%81%E6%97%A5%EF%BC%89%E7%9A%84%E8%BF%90%E5%8A%A8%E6%8E%92%E6%9C%9F%E8%A1%A8-%F0%9F%93%85" tabindex="-1">🏋️ 第四部分：休息日（周三、五、六、日）的运动排期表 📅</h2><p>一周练 <strong>4天</strong> 🗓️，分布在不用加班的晚上和周末白天。周三和周五是&quot;有氧日&quot; 🏃，周六是&quot;力量+步行日&quot; 💪🚶，周日是&quot;放松恢复日&quot; 🧘。</p><blockquote><p>⚠️ <strong>每次训练后必须做5-8分钟拉伸</strong> 🧘‍♂️，重点放松小腿、腘绳肌、大腿前侧、臀部 🦵，每个动作维持25秒 ⏱️。大体重人群不拉伸，第二天腿疼到怀疑人生 😭。</p></blockquote><h3 id="%F0%9F%94%B9-%E5%91%A8%E4%B8%89%EF%BC%88%E6%AD%A3%E5%B8%B8%E4%B8%8B%E7%8F%AD%EF%BC%89%EF%BC%9A%F0%9F%9A%B4-%E6%88%B7%E5%A4%96%E5%B1%B1%E5%9C%B0%E8%BD%A6-%E6%88%96-%F0%9F%8F%8A-%E6%B8%B8%E6%B3%B3-%E4%BA%8C%E9%80%89%E4%B8%80" tabindex="-1">🔹 周三（正常下班）：🚴 户外山地车 或 🏊 游泳 二选一</h3><ul><li><strong>🚴 选项A（户外骑行）</strong>：总时长 <strong>45分钟</strong> ⏱️，约7.5~9km 📏。前5分钟慢骑热身 🟢，中间30分钟维持心率 <strong>145~160</strong> ❤️（体感：微喘但能说出完整句子 🗣️），最后10分钟慢骑冷身 🔵。优先平缓路段，减少暴力爬坡 ⛰️🚫，档位避开极端斜链组合，保护传动套件 ⚙️。里程不重要，时间凑够就行 ✅。</li><li><strong>🏊 选项B（游泳）</strong>：总时长 <strong>40分钟</strong> ⏱️。10分钟慢游热身 🟢 + 25分钟间歇循环（游100m，漂浮休息30s）🔄 + 5分钟放松 😌。心率控制135~155 ❤️。游泳后<strong>必须</strong>在30分钟内吃1个水煮蛋或喝1勺蛋白粉 🥚💪，否则晚上会饿到暴食 🍔⚠️。</li></ul><h3 id="%F0%9F%94%B9-%E5%91%A8%E4%BA%94%EF%BC%88%E6%AD%A3%E5%B8%B8%E4%B8%8B%E7%8F%AD%EF%BC%89%EF%BC%9A%F0%9F%9A%B4-%E5%9B%BA%E5%AE%9A%E9%98%BB%E5%B0%BC%E5%8D%95%E8%BD%A6%C2%B7%E5%8F%98%E9%80%9F%E9%AA%91%EF%BC%8840%E5%88%86%E9%92%9F%EF%BC%89%E2%AD%90" tabindex="-1">🔹 周五（正常下班）：🚴 固定阻尼单车·变速骑（40分钟）⭐</h3><p>因为家里的单车不能调阻尼 🔧，我靠 <strong>“踏频（踩踏速度）”</strong> 和 <strong>“站姿/坐姿”</strong> 来模拟强度变化 🎚️。具体操作表如下（盯着单车上的速度表或心率表执行 👀）：</p><table><thead><tr><th style="text-align:left">时间段</th><th style="text-align:left">动作模式</th><th style="text-align:left">踏频目标（圈/分钟）</th><th style="text-align:left">心率目标</th><th style="text-align:left">作用</th></tr></thead><tbody><tr><td style="text-align:left">🟢 0~5分钟</td><td style="text-align:left">坐姿，匀速轻踩</td><td style="text-align:left">60~65 rpm</td><td style="text-align:left">110~125</td><td style="text-align:left">唤醒膝关节，润滑关节液 💧</td></tr><tr><td style="text-align:left">🔥 6~20分钟</td><td style="text-align:left">坐姿，加速高频踩</td><td style="text-align:left"><strong>85~90 rpm</strong> ⭐</td><td style="text-align:left">145~160</td><td style="text-align:left">黄金燃脂主区间，此时脂肪供能比例最大 💪</td></tr><tr><td style="text-align:left">🔵 21~23分钟</td><td style="text-align:left">坐姿，大幅降速缓踩</td><td style="text-align:left">60 rpm</td><td style="text-align:left">≤135</td><td style="text-align:left">短暂缓冲，排乳酸，防头晕 😵</td></tr><tr><td style="text-align:left">⚡ 24~35分钟</td><td style="text-align:left"><strong>站姿慢踩 + 坐姿高频 交替</strong>（每2分钟切换一次）🔄</td><td style="text-align:left">站姿50 rpm / 坐姿85 rpm</td><td style="text-align:left">150~165</td><td style="text-align:left">站姿模拟爬坡 ⛰️，强化大腿和臀肌 🦵；坐姿拉心率，间歇刺激 🔥</td></tr><tr><td style="text-align:left">🧊 36~40分钟</td><td style="text-align:left">坐姿，逐渐减速至停止</td><td style="text-align:left">60 → 40 rpm</td><td style="text-align:left">降至≤120</td><td style="text-align:left">冷身，让血液回流，防第二天腿疼 🦵✅</td></tr></tbody></table><blockquote><p>🚫 <strong>全程禁止站立猛踩</strong> ⚠️，固定阻尼单车站姿发力对膝关节压力极大 🦵💥，大体重人群尤其要保护膝盖 🛡️。</p></blockquote><h3 id="%F0%9F%94%B9-%E5%91%A8%E5%85%AD%EF%BC%88%E5%85%A8%E5%A4%A9%E8%87%AA%E7%94%B1%EF%BC%89%EF%BC%9A%F0%9F%92%AA-%E5%8A%9B%E9%87%8F%E8%AE%AD%E7%BB%83%EF%BC%8825%E5%88%86%E9%92%9F%EF%BC%89%2B-%F0%9F%9A%B6-%E9%95%BF%E8%B7%9D%E7%A6%BB%E6%AD%A5%E8%A1%8C%EF%BC%8860%E5%88%86%E9%92%9F%EF%BC%89%E2%AD%90%E2%AD%90" tabindex="-1">🔹 周六（全天自由）：💪 力量训练（25分钟）+ 🚶 长距离步行（60分钟）⭐⭐</h3><p><strong>顺序不能错</strong> ❗：先做力量（消耗糖原 🔋），休息10分钟后 ☕，再去长距离步行（直接燃烧脂肪 🔥）。</p><p><strong>💪 力量训练（居家自重，无需器械）</strong> 🏠：</p><ul><li>🍑 臀桥：4组 × 18次（顶峰收缩停2秒 ⏱️，强化臀大肌，减轻爬楼膝盖负担 🦵）</li><li>🦵 徒手深蹲：3组 × 14次（下蹲时膝盖对准脚尖方向 👀）</li><li>🚶 箭步蹲：3组 × 11次 / 每条腿（平衡左右下肢力量 ⚖️）</li><li>🦶 站姿提踵：4组 × 20次（缓解爬楼小腿酸痛 🦵）</li><li>🧱 平板支撑：3组 × 40秒（核心收紧，稳定骑行姿态 🚴）</li></ul><p>组间休息60秒 ⏱️，总耗时严格控制在25~35分钟内 ⌛。</p><p><strong>🚶 长距离步行</strong>：</p><p>换双软底鞋 👟，出门快走 <strong>60分钟</strong>（约5.5~6公里）📏。心率维持在 <strong>120~135</strong> ❤️（体感：能边走边哼歌 🎵）。这一步是&quot;脂肪燃烧的扫尾工作&quot; 🧹，把刚才力量训练激发的后燃效应最大化 🔥。</p><blockquote><p>🍔 <strong>周六中午设为每周放松餐</strong> 🎉⭐：可以吃想吃的东西，不计算热量 📊🚫。但仅限一餐，不是一整天放开 🙅。吃完后当天晚餐恢复正常饮食 🍽️。放松餐的意义是防止长期压抑导致报复性暴食 😋⚠️。</p></blockquote><h3 id="%F0%9F%94%B9-%E5%91%A8%E6%97%A5%EF%BC%88%E7%BA%AF%E6%94%BE%E6%9D%BE%EF%BC%89%EF%BC%9A%F0%9F%A7%98-%E6%81%A2%E5%A4%8D%E6%80%A7%E6%B8%B8%E6%B3%B3-%E6%88%96-%E5%BD%BB%E5%BA%95%E8%BA%BA%E5%B9%B3-%F0%9F%98%B4" tabindex="-1">🔹 周日（纯放松）：🧘 恢复性游泳 或 彻底躺平 😴</h3><ul><li>如果本周感觉疲劳 😩，周日 <strong>只游泳30分钟</strong> 🏊，慢悠悠地游，不计时不计速 ⏱️🚫，纯粹为了放松肌肉和水疗按摩 💆。</li><li>如果本周感觉精力尚可 ✨，周日改为 <strong>陪家人遛弯90分钟</strong> 👨‍👩‍👧 或 <strong>在家做全身拉伸20分钟</strong> 🧘。</li><li><strong>🛡️ 核心原则</strong>：周日不安排任何高强度训练 🚫🔥，为下周一的加班熬夜储备体力 🔋。</li></ul><hr /><h2 id="%F0%9F%A5%97-%E7%AC%AC%E4%BA%94%E9%83%A8%E5%88%86%EF%BC%9A%E9%A5%AE%E9%A3%9F%22%E9%A5%B1%E5%92%8C%E6%84%9F%22%E5%AE%9E%E6%93%8D%E6%B8%85%E5%8D%95%EF%BC%88%E7%85%A7%E7%9D%80%E4%B9%B0%EF%BC%8C%E4%B8%8D%E8%B4%B9%E8%84%91%EF%BC%89%F0%9F%9B%92" tabindex="-1">🥗 第五部分：饮食&quot;饱和感&quot;实操清单（照着买，不费脑）🛒</h2><p>为了避免饿肚子 😋，我的冰箱和工位抽屉里常备以下食物 🧊。记住一个替换法则：<strong>用体积换热量</strong> 📐。</p><table><thead><tr><th style="text-align:left">类别</th><th style="text-align:left">✅ 推荐食物（无限接近随便吃）</th><th style="text-align:left">⚠️ 限量吃（注意克重）</th><th style="text-align:left">🚫 禁止吃（一口都别碰）</th></tr></thead><tbody><tr><td style="text-align:left"><strong>🍚 主食</strong></td><td style="text-align:left">蒸南瓜、冬瓜、白萝卜 🎃🥬</td><td style="text-align:left">燕麦60g、红薯/玉米120g、杂粮饭130~150g 🥣🍠🌽</td><td style="text-align:left">白米饭/面条/馒头/面包（除非午餐吃熟重150g以内）🍜🍞🚫</td></tr><tr><td style="text-align:left"><strong>💪 蛋白质</strong></td><td style="text-align:left">鸡蛋白、无糖酸奶、纯牛奶 🥚🥛</td><td style="text-align:left">鸡胸肉、瘦牛肉、鱼虾（每天共150~160g）🍗🥩🐟</td><td style="text-align:left">肥牛卷、五花肉、炸鸡排、腊肠 🥓🍗🚫</td></tr><tr><td style="text-align:left"><strong>🥬 蔬菜</strong></td><td style="text-align:left"><strong>绿叶菜（菠菜/油菜/生菜）、黄瓜、番茄、西兰花、菌菇</strong> 🥦🍄（每天至少吃够500g，用清炒/水煮）</td><td style="text-align:left">土豆、莲藕、山药（算主食）🥔</td><td style="text-align:left">油炸蔬菜（地三鲜、干锅花菜）🍆🚫</td></tr><tr><td style="text-align:left"><strong>🥑 脂肪</strong></td><td style="text-align:left">橄榄油（喷两下）、坚果（每天只吃10g）🫒🥜</td><td style="text-align:left">蛋黄（每天最多2个）🥚</td><td style="text-align:left">动物油、黄油、人造奶油 🧈🚫</td></tr></tbody></table><p><strong>🍽️ 一个具体的执行日食谱示例（周六休息日 · 约2100kcal）📋</strong>：</p><ul><li><strong>🌅 早餐（08:00）</strong>：全麦面包1片 🍞 + 无糖花生酱5g 🥜 + 黑咖啡1杯 ☕ + 水煮蛋2个 🥚。</li><li><strong>🕛 午餐（12:00）</strong>：糙米饭130g 🍚 + 清蒸鲈鱼200g 🐟 + 蒜蓉西兰花300g 🥦（先吃菜和肉，最后吃米饭 🍚）。</li><li><strong>🕒 运动前加餐（15:00）</strong>：力量训练前1小时，吃1根香蕉 🍌（防低血糖 😵）。</li><li><strong>🕡 晚餐（18:30）</strong>：蒸南瓜200g 🎃 + 卤牛肉150g 🥩 + 凉拌黄瓜海带丝300g 🥒。</li><li><strong>🕢 运动后补给（19:30）</strong>：乳清蛋白1勺 或 水煮蛋1~2个 🥚💪，运动结束30~60分钟内完成 ⏱️。</li><li><strong>🌙 睡前（22:00）</strong>：如果不饿就跳过 🙈，如果饿就喝100ml热牛奶 🥛。</li></ul><blockquote><p>📌 <strong>休息日调整</strong> 📊：午、晚餐主食各减少20~30g 🍚，蛋白、蔬菜保持不变 💪🥬，不盲目砍饭 🙅。</p></blockquote><hr /><h2 id="%F0%9F%9A%A8-%E7%AC%AC%E5%85%AD%E9%83%A8%E5%88%86%EF%BC%9A%E5%BA%94%E6%80%A5%E8%B0%83%E6%95%B4%E4%B8%8E%E5%B9%B3%E5%8F%B0%E6%9C%9F%E7%A0%B4%E5%B1%80%E6%B3%95-%F0%9F%9B%A0%EF%B8%8F" tabindex="-1">🚨 第六部分：应急调整与平台期破局法 🛠️</h2><p>总会有加班到崩溃 😩、下雨没法骑车 🌧️、或者体重连续两周不动的时刻 ⚖️。针对此，我设置了五条底线 🛡️：</p><ol><li><strong>😴 如果加班到晚上10点且极度疲惫</strong>：直接放弃&quot;安抚餐&quot;中的蛋白和15分钟极简力量 🚫💪，只喝温水就睡 💧。身体需要休息时，少摄入比多运动更重要 ⚠️。</li><li><strong>📉 如果体重卡在某个数值超过2周不动</strong>：不要减少主食 🍚🚫，优先排查油脂、零食饮料摄入 🧐，<strong>每日削减150~200大卡（从脂肪和零食里砍，不动主食）</strong> 🔥。同时把周三的户外骑行换成<strong>高强度间歇游泳</strong> 🏊⚡（冲刺游25米+慢游25米交替，持续20分钟），刺激一下心肺 ❤️，平台期就破了 ✅。</li><li><strong>🍔 如果某天暴食吃了一顿大餐（比如聚餐）</strong>：不催吐 🚫🤮、不疯狂加练 🚫🏃。第二天只做一件事——午餐和晚餐的<strong>主食全部换成冬瓜/南瓜</strong> 🎃🥬，蛋白质和蔬菜翻倍 💪🥬，一天内就把多余的水分排出去 💧，体重就会回来 ⚖️。</li><li><strong>😋 如果持续饥饿、训练乏力、工作精神差</strong>：立刻上调碳水摄入量 🍚⬆️，运动日前一餐适度增加主食 🍞，热量缺口不可过大 ⚠️。减脂不能以牺牲工作表现为代价 💼。</li><li><strong>🌧️ 如果天气恶劣或身心极度疲惫</strong>：切换室内动感单车或快走 🚴🚶，不强行外出骑行 🚫🌧️。习惯的延续优先于训练打卡 ✅。</li></ol><hr /><h2 id="%F0%9F%8F%86-%E7%AC%AC%E4%B8%83%E9%83%A8%E5%88%86%EF%BC%9A%E7%BB%99%E5%B9%B4%E5%BA%95%E8%87%AA%E5%B7%B1%E7%9A%84%E9%AA%8C%E6%94%B6%E6%A0%87%E5%87%86-%F0%9F%8E%AF" tabindex="-1">🏆 第七部分：给年底自己的验收标准 🎯</h2><p>2026年12月31日早上 🌅，我不需要看到腹肌 💪🚫，也不需要能跑半马 🏃🚫。我只需要确认以下 <strong>4个硬指标</strong> 达成即可宣告胜利 🎉：</p><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> ⚖️ <strong>体重秤读数稳定在 85.0 ~ 87.0 kg 之间</strong>（连续三天晨重）。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 🦵 <strong>爬6层楼梯不再有次日剧烈酸痛</strong>（证明臀肌和腘绳肌被激活，膝盖压力减小 🛡️）。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 🚴 <strong>固定单车能轻松连续踩完40分钟&quot;站坐交替&quot;</strong>（心肺耐力提升一个档次 ⬆️）。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 📏 <strong>腰围（肚脐水平）缩减至少 8~10cm</strong>（内脏脂肪显著减少，这是健康的核心标志 ❤️）。</li></ul><blockquote><p>✅ <strong>放宽标准</strong> 🤝：若年末体重落在87~89kg，但腰围缩减≥6cm、爬楼不酸痛、运动习惯已建立 🔄，同样算成功 🏆。数字只是结果 📊，体能变好、精力提升才是更重要的收获 ✨。</p></blockquote><hr /><h2 id="%F0%9F%92%8C-%E5%86%99%E5%9C%A8%E6%9C%80%E5%90%8E%EF%BC%9A%E4%B8%8D%E8%BF%BD%E6%B1%82%E5%AE%8C%E7%BE%8E%EF%BC%8C%E5%8F%AA%E8%BF%BD%E6%B1%82%22%E5%8F%AF%E6%8C%81%E7%BB%AD%22-%F0%9F%94%84" tabindex="-1">💌 写在最后：不追求完美，只追求&quot;可持续&quot; 🔄</h2><p>这套方案最狠的地方不在于运动量 🏋️，而在于 <strong>“彻底放弃了工作日晚上必须做有氧的执念”</strong> 🧠✨。既然加班是无法改变的事实 💼，那就把睡眠、预晚餐和15分钟极简力量做到极致 😴🍽️💪。</p><p>到年底还有18周 🗓️，我不需要每天都做得满分 💯🚫，只要做到 <strong>80分</strong> ✅（比如每周漏掉1次游泳 🏊，或者有2天没控制住吃了小零食 🍪），依然能稳稳达成目标 🎯。每周1次放松餐不是破功 🍔，是为了让这套方案能跑满18周而设计的减压阀 🧘。</p><p>希望年底回看这篇博文时 📖，我能轻松地写下一句备注 ✍️：“目标达成 🎉，2027年春天准备向82kg发起快乐冲刺 🌸💪。”</p><p>共勉。💪🔥</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2026 待办事项模版]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2026-todo-list" />
                <id>tag:https://fx67ll.xyz,2026-08-28:2026-todo-list</id>
                <published>2026-08-28T20:28:00+08:00</published>
                <updated>2026-09-04T15:32:03+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%F0%9F%93%8B-%E8%BF%91%E6%9C%9F%E5%BE%85%E5%8A%9E%E4%BA%8B%E9%A1%B9%E6%80%BB%E8%A7%88" tabindex="-1">📋 近期待办事项总览</h1><hr /><h2 id="%F0%9F%94%A5-%E4%BB%8A%E6%97%A5%E5%BF%85%E5%8A%9E%EF%BC%88%E6%9C%80%E9%AB%98%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%89" tabindex="-1">🔥 今日必办（最高优先级）</h2><hr /><h2 id="%F0%9F%93%85-%E8%BF%91%E6%9C%9F%E5%BE%85%E6%8E%A8%E8%BF%9B%EF%BC%887%E5%A4%A9%E5%86%85%EF%BC%89" tabindex="-1">📅 近期待推进（7天内）</h2><hr /><h2 id="%F0%9F%93%8C-%E4%B8%80%E6%AC%A1%E6%80%A7%E5%BE%85%E5%8A%9E%E4%BB%BB%E5%8A%A1" tabindex="-1">📌 一次性待办任务</h2><blockquote><p>按场景分类，标注复杂度：🟢 简单｜🟡 中等｜🔴 偏高</p></blockquote><hr /><h2 id="%F0%9F%94%84-%E5%91%A8%E6%9C%9F%E6%80%A7%E6%97%A5%E5%B8%B8%E4%BB%BB%E5%8A%A1" tabindex="-1">🔄 周期性日常任务</h2><blockquote><p>长期坚持，按节奏打卡</p></blockquote><hr /><h3 id="%E4%B8%80%E3%80%81%F0%9F%90%B1-%E5%85%83%E5%AE%9D%E7%94%9F%E6%B4%BB%E7%AE%A1%E7%90%86" tabindex="-1">一、🐱 元宝生活管理</h3><h4 id="%E9%A9%B1%E8%99%AB%E8%AE%B0%E5%BD%95" tabindex="-1">驱虫记录</h4><ul><li>内驱：2026年8月6日 ✅</li><li>外驱：2026年8月26日 ✅</li></ul><h4 id="%E6%97%A5%E5%B8%B8%E7%85%A7%E6%96%99" tabindex="-1">日常照料</h4><ul><li>带元宝出去玩</li><li>喂元宝小零食</li><li>元宝水碗换水、饭碗清洗</li><li>元宝屎盆子定期换猫砂</li></ul><hr /><h3 id="%E4%BA%8C%E3%80%81%F0%9F%8F%83-%E5%BC%BA%E5%88%B6%E5%81%A5%E5%BA%B7%E7%AE%A1%E7%90%86" tabindex="-1">二、🏃 强制健康管理</h3><ul><li>🚴 骑车（日常/强制）</li><li>🍎 晚上禁食，过渡期用苹果、香蕉平替</li><li>😴 12点前强制上床睡觉，调理作息</li><li>🛏️ 每天1点前睡觉</li></ul><hr /><h3 id="%E4%B8%89%E3%80%81%F0%9F%A7%B9-%E6%AF%8F%E6%97%A5%E6%89%93%E5%8D%A1%EF%BC%88ibetter%EF%BC%89" tabindex="-1">三、🧹 每日打卡（iBetter）</h3><ul><li>全屋开窗通风换气</li><li>吸尘器清洁全屋</li><li>铲猫砂</li><li>整理客厅杂物</li><li>洗衣服、晾衣服、收衣服</li><li>洗碗、刷锅、清理灶台</li><li>加湿器加水（冬日限定）</li></ul><h4 id="%E6%97%A5%E5%B8%B8%E6%B5%81%E7%A8%8B%E9%93%BE" tabindex="-1">日常流程链</h4><ul><li>喂猫 → 打扫卫生</li><li>洗饭盒 → 做菜（可选）</li><li>铲猫砂 → 洗衣晾衣服</li><li>刷牙 → 洗澡</li></ul><hr /><h3 id="%E5%9B%9B%E3%80%81%F0%9F%97%93%EF%B8%8F-%E5%AE%9A%E6%97%B6%E6%B8%85%E7%90%86%EF%BC%88ibetter%EF%BC%89" tabindex="-1">四、🗓️ 定时清理（iBetter）</h3><ul><li>清理冰箱和厨房过期食品</li><li>收拾垃圾桶、套新垃圾袋</li><li>家里大扫除，主要清理各处灰尘</li><li>卫生间发霉和水渍清理</li><li>拖地、洗碗、洗衣服等日常琐事逐一处理</li><li>地平线抽奖 + 季节任务</li></ul><hr /><h3 id="%E4%BA%94%E3%80%81%F0%9F%93%9E-%E5%91%A8%E5%BA%A6%E4%BB%BB%E5%8A%A1%EF%BC%88teamabtiom%E6%89%93%E5%8D%A1%EF%BC%89" tabindex="-1">五、📞 周度任务（Teamabtiom打卡）</h3><ul><li>每周打一次视频电话给老妈 / 回家吃一次饭</li></ul><hr /><h3 id="%E5%85%AD%E3%80%81%F0%9F%93%86-%E6%9C%88%E5%BA%A6%E4%BB%BB%E5%8A%A1%EF%BC%88teamabtiom%E6%89%93%E5%8D%A1%EF%BC%89" tabindex="-1">六、📆 月度任务（Teamabtiom打卡）</h3><ul><li>每月至少回外公外婆家看看（年龄大了，机会有限）</li><li>每月至少完成一次博客全站数据备份</li></ul><hr /><blockquote><p>📌 <strong>复杂度说明</strong>：🟢 简单（短时可完成）｜🟡 中等（耗时一般、步骤较少）｜🔴 偏高（步骤多、耗时久、需统筹）</p><p>⏰ <strong>执行建议</strong>：今日必办优先清 → 近期任务穿插安排 → 一次性任务逐步推进 → 周期任务每日/每周/每月按节奏打卡</p></blockquote>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2026 洗衣机程序备忘]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2026-washer-setting" />
                <id>tag:https://fx67ll.xyz,2026-08-18:2026-washer-setting</id>
                <published>2026-08-18T18:49:50+08:00</published>
                <updated>2026-08-19T09:24:01+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%F0%9F%93%8A-%E7%B1%B3%E5%AE%B6%E5%88%86%E5%8C%BA%E6%B4%97%E7%83%98%E4%B8%80%E4%BD%93%E6%9C%BA-15kg-%E5%AE%8C%E6%95%B4%E7%A8%8B%E5%BA%8F%E6%95%B0%E6%8D%AE%E8%A1%A8" tabindex="-1">📊 米家分区洗烘一体机 15kg 完整程序数据表</h1><p>家里的洗衣机模式太多了，研究了下在线说明书，发现还是有不少讲究的点，补充一下程序数据用来备忘~</p><h2 id="%E4%B8%80%E3%80%81%E9%9D%A2%E6%9D%BF%E7%A8%8B%E5%BA%8F%E5%8F%AF%E9%80%89%E9%A1%B9" tabindex="-1">一、面板程序可选项</h2><table><thead><tr><th>程序名称</th><th>洗涤筒</th><th>洗涤容量(kg)   上筒 XHQG150XM21</th><th>洗涤容量(kg)   下筒 XHQG150XM21</th><th>烘干容量(kg)   下筒 XHQG150XM21</th></tr></thead><tbody><tr><td>混合洗</td><td>通用</td><td>5</td><td>10</td><td>7</td></tr><tr><td>快速洗</td><td>通用</td><td>1</td><td>2</td><td>2</td></tr><tr><td>强力洗</td><td>通用</td><td>5</td><td>10</td><td>7</td></tr><tr><td>大件洗</td><td>通用</td><td>3</td><td>6</td><td>6</td></tr><tr><td>棉麻洗</td><td>通用</td><td>5</td><td>10</td><td>7</td></tr><tr><td>单脱水</td><td>通用</td><td>5</td><td>10</td><td>7</td></tr><tr><td>漂+脱</td><td>通用</td><td>5</td><td>10</td><td>7</td></tr><tr><td>轻柔洗</td><td>通用</td><td>2</td><td>4</td><td>—</td></tr><tr><td>羽绒服</td><td>通用</td><td>2</td><td>4</td><td>—</td></tr><tr><td>除螨洗</td><td>通用</td><td>1</td><td>3</td><td>3</td></tr><tr><td>护色洗</td><td>通用</td><td>1</td><td>2</td><td>2</td></tr><tr><td>筒自洁</td><td>通用</td><td>0</td><td>0</td><td>—</td></tr><tr><td>自定义</td><td>通用</td><td>—</td><td>—</td><td>—</td></tr><tr><td>羊毛洗</td><td>上筒</td><td>1</td><td>—</td><td>—</td></tr><tr><td>婴童洗</td><td>上筒</td><td>3</td><td>—</td><td>—</td></tr><tr><td>内衣洗</td><td>上筒</td><td>2</td><td>—</td><td>—</td></tr><tr><td>单烘干</td><td>下筒</td><td>—</td><td>—</td><td>7</td></tr><tr><td>快洗烘</td><td>下筒</td><td>—</td><td>1</td><td>1</td></tr><tr><td>空气洗</td><td>下筒</td><td>—</td><td>—</td><td>1</td></tr></tbody></table><h2 id="%E4%BA%8C%E3%80%81app%E7%A8%8B%E5%BA%8F%E5%8F%AF%E9%80%89%E9%A1%B9" tabindex="-1">二、APP程序可选项</h2><table><thead><tr><th>程序名称</th><th>洗涤筒</th><th>洗涤容量(kg)   上筒 XHQG150XM21</th><th>洗涤容量(kg)   下筒 XHQG150XM21</th><th>烘干容量(kg)   下筒 XHQG150XM21</th></tr></thead><tbody><tr><td>新衣洗</td><td>通用</td><td>1</td><td>2</td><td>2</td></tr><tr><td>衬衣洗</td><td>通用</td><td>2</td><td>4</td><td>4</td></tr><tr><td>高温洗</td><td>通用</td><td>3</td><td>6</td><td>6</td></tr><tr><td>真丝洗</td><td>通用</td><td>1</td><td>2</td><td>—</td></tr><tr><td>冲锋衣</td><td>通用</td><td>2</td><td>4</td><td>—</td></tr><tr><td>除菌洗</td><td>通用</td><td>1</td><td>3</td><td>3</td></tr><tr><td>化纤洗</td><td>通用</td><td>3</td><td>6</td><td>6</td></tr><tr><td>牛仔洗</td><td>通用</td><td>2</td><td>4</td><td>4</td></tr></tbody></table><blockquote><p>说明：表中&quot;—&quot;表示该程序不支持对应筒的洗涤/烘干功能。</p></blockquote>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2026 家庭置业记录]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/house-longfor-desensitization" />
                <id>tag:https://fx67ll.xyz,2026-08-08:house-longfor-desensitization</id>
                <published>2026-08-08T08:08:08+08:00</published>
                <updated>2026-09-03T11:30:20+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%E5%AE%B6%E5%BA%AD%E7%BD%AE%E4%B8%9A%E8%AE%B0%E5%BD%95%EF%BC%9A%E8%B7%A8%E5%9F%8E%E7%BD%AE%E6%8D%A2%E8%B4%AD%E6%88%BF%E5%86%B3%E7%AD%96" tabindex="-1">家庭置业记录：跨城置换购房决策</h1><blockquote><p>写在前面：本文为家庭重大置业决策的个人复盘笔记，结合家庭实际情况与公开信息整理而成，仅作个人资料存档参考。</p></blockquote><h2 id="%F0%9F%93%8B-%E4%B8%80%E3%80%81%E5%86%B3%E7%AD%96%E5%9F%BA%E7%A1%80%E7%9B%98%EF%BC%9A%E6%89%80%E6%9C%89%E5%89%8D%E6%8F%90%E4%BF%A1%E6%81%AF%E4%B8%80%E8%A7%88" tabindex="-1">📋 一、决策基础盘：所有前提信息一览</h2><h3 id="%EF%BC%88%E4%B8%80%EF%BC%89%E5%AE%B6%E5%BA%AD%E5%9F%BA%E6%9C%AC%E9%9D%A2" tabindex="-1">（一）家庭基本面</h3><table><thead><tr><th style="text-align:left">类别</th><th style="text-align:left">详细信息</th></tr></thead><tbody><tr><td style="text-align:left"><strong>家庭成员</strong></td><td style="text-align:left">fx67ll、伴侣、父母（均已退休）</td></tr><tr><td style="text-align:left"><strong>月度收入</strong></td><td style="text-align:left">fx67ll 到手一万多；伴侣到手不到一万（不纳入强制还款测算，为隐性缓冲）；父母退休金合计一万出头</td></tr><tr><td style="text-align:left"><strong>现有房产</strong></td><td style="text-align:left">① 新一线近郊板块自住房B：fx67ll 名下，组合贷，月供大几千，公积金月抵扣大几百<br>② 三线城市老家老旧步梯老房：父母名下，步梯3楼（总高7层），本地老牌公办学区，挂牌大几十万，保守底价大几十万，乐观预期大几十万</td></tr><tr><td style="text-align:left"><strong>产权预案</strong></td><td style="text-align:left">新房登记在伴侣名下，男方父母全额支付首付，计划通过婚前财产约定明确权益划分：<strong>首付对应房屋价值份额归男方父母所有，婚前还贷+婚后还贷及对应增值均归女方个人所有</strong></td></tr><tr><td style="text-align:left"><strong>长期规划</strong></td><td style="text-align:left">婚后不再置换，极端情况可出售一套房全家同住</td></tr></tbody></table><h3 id="%EF%BC%88%E4%BA%8C%EF%BC%89%E7%9B%AE%E6%A0%87%E6%88%BF%E6%BA%90%E6%A0%B8%E5%BF%83%E5%8F%82%E6%95%B0" tabindex="-1">（二）目标房源核心参数</h3><table><thead><tr><th style="text-align:left">项目</th><th style="text-align:left">详情</th></tr></thead><tbody><tr><td style="text-align:left"><strong>房源位置</strong></td><td style="text-align:left">目标楼盘A，中低楼层（楼栋总高27层），精装交付</td></tr><tr><td style="text-align:left"><strong>总价预算</strong></td><td style="text-align:left">原价一百多万，叠加人才房票补贴+房企优惠后，目标落地一百多万</td></tr><tr><td style="text-align:left"><strong>付款方案</strong></td><td style="text-align:left">首付大几十万（男方父母自有资金支付），剩余贷款一百多万，全额公积金贷款（伴侣名义），月供大几千，伴侣公积金月抵扣大几百</td></tr><tr><td style="text-align:left"><strong>入住成本</strong></td><td style="text-align:left">精装交付基础上，预算约10万元（5万定制柜 +5万新增家电/杂费），大家电、家具从三线城市老房搬迁</td></tr><tr><td style="text-align:left"><strong>持有成本</strong></td><td style="text-align:left">无新增车位需求，仅物业费+公摊水电，小几百元/月</td></tr><tr><td style="text-align:left"><strong>交房时间</strong></td><td style="text-align:left">合同约定交付时间为一年半之后，父母预计交付当年年底入住</td></tr></tbody></table><h3 id="%EF%BC%88%E4%B8%89%EF%BC%89%E5%9B%9B%E5%A4%A7%E6%A0%B8%E5%BF%83%E8%AF%89%E6%B1%82" tabindex="-1">（三）四大核心诉求</h3><ol><li><strong>代际就近居住</strong>：父母从三线城市迁居新一线近郊板块，与fx67ll住同一片区，实现就近养老、婚后帮带娃，互相照应</li><li><strong>资产优化置换</strong>：淘汰老旧步梯房，置换品牌新房，提升居住品质与资产保值性</li><li><strong>教育长期布局</strong>：落户新一线获取本地学籍，提升孩子升学容错率，拓宽成长视野</li><li><strong>政策红利最大化</strong>：抓住未婚窗口期，用伴侣人才房票+全额公积金贷款，压低购房总成本</li></ol><h3 id="%EF%BC%88%E5%9B%9B%EF%BC%89%E5%AD%A6%E5%8C%BA%E4%BF%A1%E6%81%AF%E6%A0%B8%E5%AE%9E%EF%BC%88%E2%9A%A0%EF%B8%8F%E9%87%8D%E8%A6%81%E4%BF%AE%E6%AD%A3%EF%BC%89" tabindex="-1">（四）学区信息核实（⚠️重要修正）</h3><h4 id="1.-%E6%9C%80%E7%BB%88%E7%BB%93%E8%AE%BA" tabindex="-1">1. 最终结论</h4><p>目标楼盘A归属片区公办九年一贯制学校，小学毕业可直升本校初中，无需二次划片。</p><ul><li>片区内还有另一所公办学校，以一条主要道路作为施教区分界。</li></ul><h4 id="2.-%E5%AD%A6%E6%A0%A1%E5%8A%9E%E5%AD%A6%E5%9F%BA%E6%9C%AC%E9%9D%A2" tabindex="-1">2. 学校办学基本面</h4><p>该学校由本地教育集团直管，办学数年，是板块高起点打造的公办九年一贯制学校，教学成绩位列<strong>板块公办上游</strong>：</p><ul><li><strong>办学成绩</strong>：统考成绩表现不错，总均分高于片区平均水平，主科表现有优势</li><li><strong>升学出口（民间汇总，仅供自家参考）</strong>：年级靠前有机会冲击市内头部高中，中上游可进入区域重点高中，升学分层清晰</li><li><strong>师资配置</strong>：集团派驻管理团队+骨干教师，新招教师学历门槛较高，硬件配置标准不错</li></ul><h4 id="3.-%E8%87%AA%E5%AE%B6%E6%A0%B8%E5%AE%9E%E5%AD%A6%E5%8C%BA%E7%9A%84%E5%87%A0%E4%B8%AA%E6%96%B9%E5%BC%8F" tabindex="-1">3. 自家核实学区的几个方式</h4><ol><li>每年招生季查阅片区发布的义务教育招生入学方案附件</li><li>招生季在本地政务服务APP录入房产信息，查看匹配施教区</li></ol><hr /><h2 id="%F0%9F%93%8A-%E4%BA%8C%E3%80%81%E5%85%AD%E5%A4%A7%E7%BB%B4%E5%BA%A6%E6%B7%B1%E5%BA%A6%E8%AF%84%E4%BC%B0%EF%BC%88%E6%BB%A1%E5%88%86100%E5%88%86%EF%BC%89" tabindex="-1">📊 二、六大维度深度评估（满分100分）</h2><h3 id="1.-%E8%B4%A2%E5%8A%A1%E5%AE%89%E5%85%A8%E4%B8%8E%E7%8E%B0%E9%87%91%E6%B5%81%E7%BB%B4%E5%BA%A6%EF%BC%9A87%E5%88%86" tabindex="-1">1. 财务安全与现金流维度：87分</h3><h4 id="%E2%9C%85-%E6%A0%B8%E5%BF%83%E4%BC%98%E5%8A%BF" tabindex="-1">✅ 核心优势</h4><ul><li><strong>负债比例处于安全区间</strong>：两套房合计现金月供合计一万多，叠加父母退休金后家庭月总收入两万多，<strong>房贷收入比约 39.9%</strong>，低于银行50%的风险警戒线，无系统性断供风险</li><li><strong>多层安全垫充足</strong>：伴侣月收入未纳入还款测算，完全可覆盖婚后日常开销与育儿支出；父母退休金可全额作为养老、医疗应急储备，极端情况下可补充月供</li><li><strong>资金无断档风险</strong>：首付为父母自有资金，三线城市老房出售仅为回笼资金，不影响购房进度；交房周期较长，有充足时间择价卖房，无需降价急售</li><li><strong>生活成本反向优化</strong>：父母同住片区后可共同就餐，按年轻夫妻工作日外食/外卖每月固定开销测算，共同就餐每月补贴两三千即可覆盖，<strong>每月可节省小几千开支</strong>，且饮食更健康</li><li><strong>成本测算务实</strong>：装修、持有成本均基于实际经验预估，无过度乐观偏差，后续超支风险低</li></ul><h4 id="%E2%9A%A0%EF%B8%8F-%E5%AE%A2%E8%A7%82%E7%9F%AD%E6%9D%BF" tabindex="-1">⚠️ 客观短板</h4><ul><li>仅靠 fx67ll 工资扣除月供后月结余只有小几千，单人覆盖全部家用会偏紧，婚后需依赖伴侣收入或父母小额贴补</li><li>家庭流动性资产集中于房产，<strong>建议单独留存约10万现金应急储备</strong>，覆盖6个月以上家庭开支，应对突发大额支出</li><li>房产急售存在15%～20%折价，无法作为即时应急资金</li></ul><h4 id="%F0%9F%92%AC-%E7%BB%B4%E5%BA%A6%E7%BB%93%E8%AE%BA" tabindex="-1">💬 维度结论</h4><blockquote><p>家庭整体财务安全边际充足，无系统性风险，做好现金储备与开支规划即可稳得住。</p></blockquote><hr /><h3 id="2.-%E5%B1%85%E4%BD%8F%E5%85%BB%E8%80%81%E4%B8%8E%E7%94%9F%E6%B4%BB%E5%93%81%E8%B4%A8%E7%BB%B4%E5%BA%A6%EF%BC%9A92%E5%88%86" tabindex="-1">2. 居住养老与生活品质维度：92分</h3><blockquote><p>这也是本次决策最被低估的加分项，除房屋本身外，板块成熟的生活配套与生态资源，是日常高频受益的部分。</p></blockquote><h4 id="%E2%9C%85-%E6%A0%B8%E5%BF%83%E4%BC%98%E5%8A%BF-1" tabindex="-1">✅ 核心优势</h4><ul><li><strong>地缘距离完美适配</strong>：和现有住房同属一个近郊板块，自驾10分钟左右可达，真正实现“一碗汤的距离”，日常送饭、带娃、老人就医均可快速响应</li><li><strong>居住品质代际升级</strong>：从三线城市老旧步梯房置换为品牌电梯新房，物业、园区绿化、无障碍设施、商业配套全面适配养老需求，父母不用爬楼</li><li><strong>日照条件达标</strong>：大寒日满窗日照5小时，远高于当地2小时法定标准，满足老人晾晒、采光需求</li><li><strong>安静度整体良好</strong>：楼栋不临城市主干道，无持续车流噪音，夜间居住安静度高，满足养胎、育儿的休息需求</li><li><strong>板块交通成熟便利</strong><ul><li><strong>轨道交通</strong>：距离片区轨道交通站点很近，步行可达，可快速通达城市核心，换乘多条线路</li><li><strong>路网交通</strong>：紧邻跨江快速路网，通勤到主城核心区效率高；片区内部主次路网完善</li><li><strong>公共交通</strong>：周边公交线路充足，老人出行便捷</li></ul></li><li><strong>商业生活配套完善</strong><ul><li>周边有大型商业综合体，步行可达超市、餐饮、影院、亲子业态</li><li>片区还有其他商业集群，日常消费、周末休闲都有去处</li></ul></li><li><strong>近郊山林生态养老加成</strong><ul><li>板块靠近近郊山林生态资源，森林覆盖率高，空气质量常年优良</li><li>山林步道平缓，适合老人日常散步、晨练、踏青；周边休闲点位车程十几分钟，养老休闲资源丰富</li><li>较好的空气环境，对老年人呼吸系统、心血管健康有正向益处，是主城很难得的生态条件</li></ul></li></ul><h4 id="%E2%9A%A0%EF%B8%8F-%E5%AE%A2%E8%A7%82%E7%9F%AD%E6%9D%BF-1" tabindex="-1">⚠️ 客观短板</h4><ul><li><strong>关于东侧公寓设备噪音的客观说明</strong>：东侧公寓的设备（空调外机、新风机组等）一般布置在楼顶或裙楼顶，低频噪音通过建筑结构向下传导效率更高、向上衰减更快。本套中低楼层位于设备层下方数层，受设备直接振动影响小于上面几层，并非噪音最严重楼层；主要影响来自日间空气传播的设备运行声，夜间停机后基本无干扰。</li><li>周边有高层楼栋环绕，中低楼层视野、通风相比高楼层略打折扣</li><li>父母迁居过来之后，会有半年到一年的社交圈、生活习惯适应期，需要慢慢熟悉周边</li></ul><h4 id="%F0%9F%92%AC-%E7%BB%B4%E5%BA%A6%E7%BB%93%E8%AE%BA-1" tabindex="-1">💬 维度结论</h4><blockquote><p>养老居住的核心需求全部满足，交通、商业、生态配套相比老房子提升明显；楼栋噪音影响比前期预想要好，小瑕疵不改变整体居住价值。</p></blockquote><hr /><h3 id="3.-%E6%95%99%E8%82%B2%E8%A7%84%E5%88%92%E4%B8%8E%E5%8F%91%E5%B1%95%E7%BB%B4%E5%BA%A6%EF%BC%9A83%E5%88%86" tabindex="-1">3. 教育规划与发展维度：83分</h3><h4 id="%E2%9C%85-%E6%A0%B8%E5%BF%83%E4%BC%98%E5%8A%BF-2" tabindex="-1">✅ 核心优势</h4><ul><li><strong>升学容错率显著提升</strong>：新一线高中分层更密集，区域内优质高中选择更多。孩子就算达不到顶尖水平，也有机会进入不错的高中，不会因为一次中考失利直接大幅掉档，匹配“孩子未必能冲到顶尖梯队”的现实顾虑。</li><li><strong>高考平台优势明确</strong>：本省本科录取率、双一流院校省内招生名额相比三线城市所在省份更占优势，同等努力下，有机会摸到更好的大学层级。</li><li><strong>学校办学有基础支撑</strong>：学校已经完整走过几届毕业班，成绩处于板块公办上游，不是完全没有验证的全新学校，集团给了师资和管理背书。</li><li><strong>城市资源更丰富</strong>：博物馆、研学、竞赛、高校资源多，孩子成长视野、综合素质提升空间更大。</li><li><strong>九年一贯制省心</strong>：小学初中连读，不用小升初再折腾择校，减少升学焦虑。</li></ul><h4 id="%E2%9A%A0%EF%B8%8F-%E5%AE%A2%E8%A7%82%E7%9F%AD%E6%9D%BF-2" tabindex="-1">⚠️ 客观短板</h4><ul><li>办学年限不算很长，顶尖生源厚度、高分段产出，比不上三线城市本地办学几十年的老牌公办，整体成绩稳定性，还需要往后<strong>5‑10年</strong>多届中考持续观察。</li><li>新一线教育内卷程度更高，课外补习、兴趣班、研学等开销，会比三线城市高出30%‑50%，长期教育成本会明显上涨。</li><li>本地中考普高录取压力同样存在，不是换个城市就稳上高中。</li><li>片区人口持续导入，未来存在学位紧张、被统筹的微小可能性。</li></ul><h4 id="%F0%9F%92%AC-%E7%BB%B4%E5%BA%A6%E7%BB%93%E8%AE%BA-2" tabindex="-1">💬 维度结论</h4><blockquote><p>长期升学赛道收益明确，容错率提升是最大价值；学校底子尚可，适合普通天赋、中上游水平的孩子。</p></blockquote><hr /><h3 id="4.-%E6%94%BF%E7%AD%96%E7%BA%A2%E5%88%A9%E4%B8%8E%E8%B4%B7%E6%AC%BE%E6%88%90%E6%9C%AC%E7%BB%B4%E5%BA%A6%EF%BC%9A92%E5%88%86" tabindex="-1">4. 政策红利与贷款成本维度：92分</h3><h4 id="%E2%9C%85-%E6%A0%B8%E5%BF%83%E4%BC%98%E5%8A%BF-3" tabindex="-1">✅ 核心优势</h4><ul><li><strong>政策窗口期把握精准</strong>：当地人才房票补贴仅限单身个人申领，婚后家庭无法享受，婚前购房是现有规则下的最优解，错过就不再有。</li><li><strong>贷款成本极低</strong>：全额公积金贷款利率远低于商贷，一百多万的贷款长期算下来可以省下大量利息，是普通家庭能拿到的最低成本房贷。</li><li><strong>首付比例友好</strong>：首套新房公积金首付20%即可办理，首付门槛可控，不会过度消耗家庭流动资金。</li></ul><h4 id="%E2%9A%A0%EF%B8%8F-%E5%AE%A2%E8%A7%82%E7%9F%AD%E6%9D%BF-3" tabindex="-1">⚠️ 客观短板</h4><ul><li>贷款资格占用不可逆：婚后家庭名下两套房贷，公积金额度全部用掉，以后再置换就没法再用公积金低息贷款。</li><li>房票补贴有对应的申领条件、服务期约束。</li><li>公积金实际审批额度，要看缴存基数、账户余额、征信综合判定，存在额度不够需要追加首付的可能性。</li></ul><h4 id="%F0%9F%92%AC-%E7%BB%B4%E5%BA%A6%E7%BB%93%E8%AE%BA-3" tabindex="-1">💬 维度结论</h4><blockquote><p>现有政策几乎吃到满，是本次决策非常亮眼的优势，做到当下规则内成本最优。</p></blockquote><hr /><h3 id="5.-%E4%BA%A7%E6%9D%83%E4%B8%8E%E8%B5%84%E4%BA%A7%E5%AE%89%E5%85%A8%E7%BB%B4%E5%BA%A6%EF%BC%9A85%E5%88%86" tabindex="-1">5. 产权与资产安全维度：85分</h3><h4 id="%E2%9C%85-%E6%A0%B8%E5%BF%83%E4%BC%98%E5%8A%BF-4" tabindex="-1">✅ 核心优势</h4><ul><li><strong>产权安排权责清晰</strong>：男方父母全额出资首付，通过婚前约定写清权益归属，情理和财产边界兼顾，减少婚前财产矛盾。</li><li><strong>资产结构全面优化</strong>：三线城市老旧步梯房长期贬值的概率很大，置换到新一线近郊品牌新房，流动性、保值抗跌能力整体提升，完成跨城资产升级。</li><li><strong>极端情况有退路</strong>：危机时可以选择出售一套房全家同住，保留资产变现兜底选项。</li></ul><h4 id="%E2%9A%A0%EF%B8%8F-%E5%AE%A2%E8%A7%82%E7%9F%AD%E6%9D%BF-4" tabindex="-1">⚠️ 客观短板</h4><ul><li>房屋登记在伴侣一人名下，法律上登记人拥有完整物权；如果婚前发生极端分歧，要通过司法途径确认出资对应的权益，会耗费时间流程。</li><li>婚后家庭两套住宅，未来出售如果不满足满五唯一，会多出个税成本。</li><li>两套房产占用购房资格，以后想要再改善置换，需要先出掉一套。</li><li>近郊板块二手房挂牌量不小，未来真要出手，成交周期大概率会比三线城市核心学区房更长，要做好长期持有的心理准备。</li></ul><h4 id="%F0%9F%92%AC-%E7%BB%B4%E5%BA%A6%E7%BB%93%E8%AE%BA-4" tabindex="-1">💬 维度结论</h4><blockquote><p>产权约定逻辑清楚，没有致命风险；资产置换逻辑成立，长期持有优于死守老破步梯。</p></blockquote><hr /><h3 id="6.-%E5%AE%B6%E5%BA%AD%E5%85%B3%E7%B3%BB%E4%B8%8E%E9%95%BF%E6%9C%9F%E8%A7%84%E5%88%92%E7%BB%B4%E5%BA%A6%EF%BC%9A90%E5%88%86" tabindex="-1">6. 家庭关系与长期规划维度：90分</h3><h4 id="%E2%9C%85-%E6%A0%B8%E5%BF%83%E4%BC%98%E5%8A%BF-5" tabindex="-1">✅ 核心优势</h4><ul><li><strong>代际距离规划科学</strong>：同片区不同住，既能互相照应，又保留各自独立生活空间，平衡养老和小家庭，避免长期同住带来的摩擦。</li><li><strong>婚前规划透明坦诚</strong>：权益提前沟通清楚，减少婚后出资、产权纠纷隐患，婚姻的起点更稳。</li><li><strong>长期规划清晰</strong>：以自住为核心，不打算反复置换，同时预留极端场景退路，思考比较成熟。</li><li><strong>全家诉求高度对齐</strong>：养老帮带、资产置换、教育布局几大诉求，一套房子尽量覆盖，家庭内部共识度高。</li></ul><h4 id="%E2%9A%A0%EF%B8%8F-%E5%AE%A2%E8%A7%82%E7%9F%AD%E6%9D%BF-5" tabindex="-1">⚠️ 客观短板</h4><ul><li>父母迁居过来，原有社交圈断掉，存在隐性适应成本。</li><li>婚后育儿、养老多重事情叠加，家庭琐事变多，对代际相处是考验。</li><li>异地医保、待遇认证等一堆手续要跑，前期会有事务成本。</li></ul><h4 id="%F0%9F%92%AC-%E7%BB%B4%E5%BA%A6%E7%BB%93%E8%AE%BA-5" tabindex="-1">💬 维度结论</h4><blockquote><p>家庭层面正向收益远大于摩擦成本，有利于全家长期稳定。</p></blockquote><hr /><h2 id="%F0%9F%8F%86-%E4%B8%89%E3%80%81%E6%9C%AC%E6%AC%A1%E5%86%B3%E7%AD%96%E7%9A%84%E6%A0%B8%E5%BF%83%E4%BB%B7%E5%80%BC%E6%80%BB%E7%BB%93" tabindex="-1">🏆 三、本次决策的核心价值总结</h2><ol><li><strong>居住价值实打实</strong>：不是纸面增值，是实实在在的生活改善。父母养老品质升级，小家庭获得就近支持，还能降低日常餐饮开销，全家都能感受得到。</li><li><strong>生态养老加成显著</strong>：近郊山林的空气、休闲资源，是老城区很难给到退休老人的长期福利。</li><li><strong>政策红利吃满</strong>：未婚人才补贴+全额公积金，拿到极低的购房成本，窗口期一旦过去，就很难复制。</li><li><strong>教育容错率升级</strong>：从三线城市比较单一的筛选模式，切换到选择更多的升学赛道，给孩子多留一些台阶，不用逼到必须冲到头部才有的出路。</li><li><strong>资产结构优化</strong>：出掉长期走弱的老旧房产，换到能级更高城市的新房，家庭资产抗风险能力提升，同时保留变现的灵活性。</li></ol><hr /><h2 id="%E2%9A%A0%EF%B8%8F-%E5%9B%9B%E3%80%81%E5%85%A8%E9%83%A8%E5%AE%A2%E8%A7%82%E7%BC%BA%E7%82%B9%E4%B8%8E%E9%A3%8E%E9%99%A9%E6%B8%85%E5%8D%95%EF%BC%88%E8%87%AA%E5%AE%B6%E8%AE%B0%E5%BD%95%EF%BC%8C%E4%B8%8D%E5%9B%9E%E9%81%BF%E9%97%AE%E9%A2%98%EF%BC%89" tabindex="-1">⚠️ 四、全部客观缺点与风险清单（自家记录，不回避问题）</h2><ol><li><strong>楼栋细节瑕疵</strong>：楼栋不临主干道，东侧公寓设备振动影响有限，但日间会有一定设备运行、社区人流噪音，夜间基本还好。</li><li><strong>学校仍处在成长阶段</strong>：虽然已经有几届毕业，板块公办上游水平，但顶尖生源和长期中考表现，还需要往后5‑10年持续观察。</li><li><strong>施教区不是永久锁死</strong>：片区生源增加、新建学校落地，施教区存在调整可能性，入学要看孩子读书那一年实际划分。</li><li><strong>个人现金流偏紧</strong>：仅靠 fx67ll 工资，扣完月供之后结余有限，婚后育儿阶段需要伴侣收入来托底。</li><li><strong>教育支出会上涨</strong>：孩子上学之后，课外相关开销，相比三线城市会多出每年一笔增量。</li><li><strong>购房资格与公积金全部占用</strong>：以后想置换改善，必须先卖掉一套；公积金实际放款额度以审批为准，有额度不及预期的可能性。</li><li><strong>父母迁居适应成本</strong>：要重新建立社交，还要跑医保异地备案等手续，前大半年会有适应过程。</li><li><strong>交付办证存在不确定性</strong>：新房存在延期交付的可能性，办证快慢也会影响落户、入学节奏。</li><li><strong>房产流动性约束</strong>：急售必然要折价，板块二手房挂牌量大，不要指望随时可以快速卖掉。</li></ol><hr /><h2 id="%F0%9F%8E%AF-%E4%BA%94%E3%80%81%E6%9C%80%E7%BB%88%E7%BB%BC%E5%90%88%E8%AF%84%E5%88%86%E4%B8%8E%E7%BB%93%E8%AE%BA" tabindex="-1">🎯 五、最终综合评分与结论</h2><h3 id="%E7%BB%BC%E5%90%88%E6%9C%80%E7%BB%88%E5%BE%97%E5%88%86%EF%BC%9A88%E5%88%86" tabindex="-1">综合最终得分：88分</h3><h3 id="%E2%9C%85-%E6%9C%80%E7%BB%88%E7%BB%93%E8%AE%BA" tabindex="-1">✅ 最终结论</h3><blockquote><p><strong>这是一项大方向正确、风险整体可控、收益明显大于代价的家庭决策，匹配自家长期规划，可以落地执行。</strong></p></blockquote><p>这次买房核心不是赌房产增值，而是用合理成本一次性解决几件大事：父母就近养老、婚后育儿支持、家庭资产调整、下一代教育平台。所有风险点都是细节层面，没有致命硬伤。</p><p>主要关心的噪音、楼层、学区、资金、产权，都已经做了预案缓冲，思考的保守度足够。买房没有完美选项，只有适配自家现实条件的选择，利弊全部摊开，就不容易后悔。</p><hr /><h2 id="%F0%9F%93%8C-%E5%85%AD%E3%80%81%E8%90%BD%E5%9C%B0%E6%89%A7%E8%A1%8C%E6%B8%85%E5%8D%95%EF%BC%88%E8%87%AA%E5%AE%B6%E5%90%8E%E7%BB%AD%E8%B7%9F%E8%BF%9B%EF%BC%89" tabindex="-1">📌 六、落地执行清单（自家后续跟进）</h2><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>选房优化</strong>：优先选远离公寓立面的单元，进一步降低设备噪音空气传播的影响</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>产权约定</strong>：婚前签署财产约定，明确两点——① 房屋首付款为男方父母全额出资，对应房屋价值份额归男方父母所有；② 婚前及婚后女方还贷部分以及对应房屋增值权益，均归女方个人所有</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>资金规划</strong>：单独留存约10万家庭应急现金，不投入房产，覆盖6个月以上家庭开支</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>卖房节奏</strong>：老房正常挂牌，不急于割肉抛售，利用较长交房周期慢慢等合适价格，优先保价格，不求速度</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>教育准备</strong>：孩子上学还有5‑10年，不必过度焦虑；持续盯落户、人户一致相关实际情况，留意施教区变化</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>父母过渡</strong>：交房前多带父母熟悉周边环境，提前办好医保异地就医备案，降低迁居适应压力</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>政策跟进</strong>：盯人才房票、公积金贷款相关规则变化，提前备好材料</li></ul><blockquote><p>记录时间：2026年年中<br />记录人：fx67ll<br />性质：个人生活记录笔记</p></blockquote><p><strong>本文所有敏感数据均已脱敏处理，完整原始精准数据版本<a href="https://fx67ll.xyz/archives/house-longfor" target="_blank">已加密归档</a></strong></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2026 里程规划记录]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/ev-mileage" />
                <id>tag:https://fx67ll.xyz,2026-08-07:ev-mileage</id>
                <published>2026-08-07T20:07:00+08:00</published>
                <updated>2026-08-08T17:34:23+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%F0%9F%9A%97-%E8%BD%A6%E8%BE%86%E9%87%8C%E7%A8%8B%E4%BD%BF%E7%94%A8%E5%88%86%E6%9E%90%E4%B8%8E%E8%A7%84%E5%88%92%E6%8A%A5%E5%91%8A-v4.0" tabindex="-1">🚗 车辆里程使用分析与规划报告 v4.0</h1><p>📅 更新截止日期：2026年08月07日<br />📍 当前总里程：<strong>72603km</strong><br />🗓️ 规划周期：2026-02-11 至 2026-10-13<br />🔗 上版基准：v3.0（2026-06-05 / 里程66813km）</p><hr /><h2 id="%E4%B8%80%E3%80%81%F0%9F%94%8B-%E5%8A%A8%E5%8A%9B%E7%94%B5%E6%B1%A0%E5%81%A5%E5%BA%B7%E7%8A%B6%E6%80%81%E4%B8%93%E9%A1%B9%E8%AF%84%E4%BC%B0" tabindex="-1">一、🔋 动力电池健康状态专项评估</h2><p>结合电动汽车三元锂动力电池特性，基于当前行驶里程做全维度健康度打分与寿命预判。</p><h3 id="1.1-%F0%9F%93%9D-%E7%94%A8%E8%BD%A6%E4%B9%A0%E6%83%AF%E5%81%A5%E5%BA%B7%E5%BA%A6%E8%AF%84%E5%88%86" tabindex="-1">1.1 📝 用车习惯健康度评分</h3><table><thead><tr><th>维度</th><th>习惯描述</th><th>健康评级</th><th>核心说明</th></tr></thead><tbody><tr><td>充电策略</td><td>日常40%-50%充至80%，家充占比95%</td><td>⭐⭐⭐⭐⭐ 满分</td><td>完美贴合三元锂浅充浅放最优区间</td></tr><tr><td>满电管理</td><td>仅出远门满电，充满立即出发，全年≤10次</td><td>⭐⭐⭐⭐⭐ 满分</td><td>完全避开满电静置老化的最大风险</td></tr><tr><td>快充频次</td><td>仅5%场景使用120-520kW快充</td><td>⭐⭐⭐⭐☆ 优秀</td><td>低频次快充几乎无额外电芯损耗</td></tr><tr><td>放电强度</td><td>50%时间运动模式跑高速</td><td>⭐⭐⭐⭐☆ 优秀</td><td>液冷热管理到位，仅轻微加速自然衰减</td></tr></tbody></table><blockquote><p>综合结论：你的用车习惯属于电车用户中的Top 5%水平，电池衰减速度远低于行业平均，完全不用担心续航闪崩、跳电等问题。</p></blockquote><h3 id="1.2-%F0%9F%93%8A-%E5%BD%93%E5%89%8D%E9%87%8C%E7%A8%8B%E5%81%A5%E5%BA%B7%E5%BA%A6%E6%B5%8B%E7%AE%97" tabindex="-1">1.2 📊 当前里程健康度测算</h3><p>等效循环基准：综合续航按 <strong>500km/次完整循环</strong> 计算<br />电芯设计寿命：≥1500次循环（容量保持率≥80%）</p><pre><code class="language-">累计等效循环次数 = 总里程 ÷ 单次循环续航= 72603km ÷ 500km/次≈ 145次</code></pre><p>✅ <strong>当前健康度推算：96% ~ 97%</strong></p><blockquote><p>参考同车型车主实测：行驶3.1万公里健康度约94.78%；你的充电习惯更科学，衰减速度更慢，当前状态处于优秀区间。</p></blockquote><h3 id="1.3-%E2%9C%A8-%E7%94%B5%E6%B1%A0%E9%95%BF%E5%AF%BF%E7%9A%84%E5%9B%9B%E5%A4%A7%E6%A0%B8%E5%BF%83%E4%BC%98%E5%8A%BF" tabindex="-1">1.3 ✨ 电池长寿的四大核心优势</h3><ul><li><p>✅ <strong>浅充浅放黄金区间</strong><br />常年将电量维持在40%-80%，电芯内部化学应力最小，是延长循环寿命的最核心手段，延寿效果远优于频繁满充满放。</p></li><li><p>✅ <strong>家充慢充为主流</strong><br />家用慢充倍率约0.3C，电流平缓柔和，电芯析锂风险极低。相比以超充为主的车辆，电池整体寿命可延长30%以上。</p></li><li><p>✅ <strong>零满电静置伤害</strong><br />三元锂在满电状态下长期静置，会加速正极材料解构和电解液老化。你仅在出发前补满电，完全规避了这一最隐蔽的损耗来源。</p></li><li><p>✅ <strong>全域液冷系统兜底</strong><br />车辆的液冷热管理可在高速持续放电时稳定电芯温度，即使运动模式也不会出现局部过热劣化，保障整包电芯长期一致性。</p></li></ul><h3 id="1.4-%F0%9F%92%A1-%E6%97%A5%E5%B8%B8%E4%BD%BF%E7%94%A8%E4%BC%98%E5%8C%96%E5%B0%8F%E8%B4%B4%E5%A3%AB" tabindex="-1">1.4 💡 日常使用优化小贴士</h3><ul><li>高速长途跑完后，低速滑行/怠速2-3分钟再熄火锁车，让散热系统带走电池余热，减少高温老化的累积效应。</li><li>偶尔使用快充无需有心理负担，你当前的快充占比完全在安全范围内，长途应急放心使用。</li><li>车机显示的健康度小幅波动属于BMS校准误差，不用因为1%-2%的浮动过度焦虑，每3-6个月做一次完整的满充满放即可校准准确。</li></ul><h3 id="1.5-%F0%9F%93%85-%E9%95%BF%E6%9C%9F%E6%8C%81%E6%9C%89%E5%AF%BF%E5%91%BD%E9%A2%84%E5%88%A4" tabindex="-1">1.5 📅 长期持有寿命预判</h3><ul><li>🕒 第3年（约10-12万公里）：健康度约 <strong>90% ~ 92%</strong>，体感续航几乎无明显缩水</li><li>🕕 第6年（约20-24万公里）：健康度约 <strong>82% ~ 86%</strong>，仍高于厂家8年质保标准</li><li>🎯 全程保障：不会出现断崖式掉电、跳电关机、限功率等故障，正常开到换车都无需自费更换电池</li></ul><hr /><h2 id="%E4%BA%8C%E3%80%81%F0%9F%93%88-%E9%87%8C%E7%A8%8B%E4%BD%BF%E7%94%A8%E5%A4%8D%E7%9B%98%E4%B8%8E%E8%A7%84%E5%88%92" tabindex="-1">二、📈 里程使用复盘与规划</h2><h3 id="2.1-%F0%9F%93%8C-%E5%9F%BA%E7%A1%80%E8%A7%84%E5%88%92%E8%A7%84%E5%88%99%EF%BC%88%E4%BF%9D%E6%8C%81%E4%B8%8D%E5%8F%98%EF%BC%89" tabindex="-1">2.1 📌 基础规划规则（保持不变）</h3><ul><li>🗓️ 规划总周期：2026-02-11 至 2026-10-13，共244天</li><li>💼 工作日总数：174天，日均通勤固定里程 <strong>75km</strong></li><li>🎉 周末/节假日总数：70天，为自由可支配里程</li><li>📏 总里程上限：80000km</li><li>🚩 周期初始里程（2026-02-11）：55850km</li><li>💰 周期内可用总里程：<strong>24150km</strong></li></ul><h3 id="2.2-%F0%9F%93%8A-%E6%88%AA%E8%87%B3%E5%BD%93%E5%89%8D%E7%B4%AF%E8%AE%A1%E8%A1%8C%E9%A9%B6%E5%A4%8D%E7%9B%98" tabindex="-1">2.2 📊 截至当前累计行驶复盘</h3><h4 id="%E2%91%A0-%E6%97%B6%E9%97%B4%E7%BB%B4%E5%BA%A6%E7%BB%9F%E8%AE%A1" tabindex="-1">① 时间维度统计</h4><ul><li>已过总天数：178天</li><li>已过工作日：125天</li><li>已过周末/节假日：53天</li><li>剩余总天数：66天</li><li>剩余工作日：49天</li><li>剩余周末/节假日：17天</li></ul><h4 id="%E2%91%A1-%E9%87%8C%E7%A8%8B%E6%B6%88%E8%80%97%E6%8B%86%E8%A7%A3" tabindex="-1">② 里程消耗拆解</h4><p>📌 <strong>累计已行驶总里程</strong></p><pre><code class="language-">总行驶里程 = 当前里程 - 初始里程= 72603km - 55850km= 16753km</code></pre><p>📌 <strong>工作日累计消耗里程</strong></p><pre><code class="language-">工作日里程 = 已过工作日 × 日均通勤里程= 125天 × 75km/天= 9375km</code></pre><p>📌 <strong>周末/节假日累计消耗里程</strong></p><pre><code class="language-">周末里程 = 总行驶里程 - 工作日里程= 16753km - 9375km= 7378km</code></pre><p>📌 <strong>日均消耗水平</strong></p><ul><li>周末/节假日日均实际里程：<code>7378 ÷ 53 ≈ 139.21km/天</code></li><li>整体日均行驶里程：<code>16753 ÷ 178 ≈ 94.12km/天</code></li></ul><h4 id="%E2%91%A2-%E7%8E%AF%E6%AF%94%E5%8F%98%E5%8C%96%E5%88%86%E6%9E%90" tabindex="-1">③ 环比变化分析</h4><p>对比v3.0（6月5日）数据：</p><ul><li>新增行驶里程：<strong>5790km</strong>（72603 - 66813）</li><li>新增时段周末日均里程约134.2km/天，较上一周期的112.57km略有回升，但仍远低于全周期规划值</li><li><em>整体使用节奏持续宽松，无任何超支风险</em></li></ul><h3 id="2.3-%F0%9F%8E%AF-%E5%89%A9%E4%BD%99%E9%87%8C%E7%A8%8B%E5%88%86%E9%85%8D%E4%B8%8E%E9%A2%84%E6%B5%8B" tabindex="-1">2.3 🎯 剩余里程分配与预测</h3><h4 id="%E2%91%A0-%E6%8C%89%E5%8E%9F%E8%A7%84%E5%88%92%E7%9A%84%E5%89%A9%E4%BD%99%E9%A2%9D%E5%BA%A6" tabindex="-1">① 按原规划的剩余额度</h4><p>📌 <strong>剩余可用总里程</strong></p><pre><code class="language-">剩余总里程 = 周期可用总里程 - 累计已行驶里程= 24150km - 16753km= 7397km</code></pre><p>📌 <strong>剩余工作日必开里程</strong></p><pre><code class="language-">剩余工作日里程 = 剩余工作日 × 日均通勤里程= 49天 × 75km/天= 3675km</code></pre><p>📌 <strong>剩余周末/节假日可用总里程</strong></p><pre><code class="language-">剩余周末里程 = 剩余总里程 - 剩余工作日里程= 7397km - 3675km= 3722km</code></pre><p>📌 <strong>剩余周末日均可用额度</strong><br /><code>3722km ÷ 17天 ≈ 218.94km/天</code></p><h4 id="%E2%91%A1-%E6%8C%89%E5%BD%93%E5%89%8D%E8%8A%82%E5%A5%8F%E7%9A%84%E6%9C%80%E7%BB%88%E9%A2%84%E6%B5%8B" tabindex="-1">② 按当前节奏的最终预测</h4><p>若保持当前周末日均139.21km的消耗节奏：</p><pre><code class="language-">剩余周末预计消耗 = 剩余周末天数 × 当前日均里程= 17天 × 139.21km/天≈ 2367km周期结束剩余自由里程 = 剩余周末额度 - 预计消耗= 3722km - 2367km≈ 1355km</code></pre><p>✅ <strong>结论</strong>：里程余量非常充足，完全可以支撑多次长途自驾游或临时出行需求。</p><h3 id="2.4-%F0%9F%92%AC-%E5%85%B3%E9%94%AE%E7%BB%93%E8%AE%BA%E4%B8%8E%E4%BD%BF%E7%94%A8%E5%BB%BA%E8%AE%AE" tabindex="-1">2.4 💬 关键结论与使用建议</h3><ol><li><p><strong>里程状态极度宽松</strong><br />当前周末日均消耗仅139km，远低于全周期规划值158.57km；剩余时段日均可用额度更是提升至219km，完全没有超支风险。</p></li><li><p><strong>长途出行放心安排</strong><br />剩余3722km的自由里程，可支撑至少3-4次单程500km级别的长途自驾游，无需压缩日常通勤或周末短途出行。</p></li><li><p><strong>后续管理建议</strong></p></li></ol><ul><li>若有国庆长途出行计划，可提前预留对应里程，当前余量完全覆盖无压力</li><li>建议每月底简单核对一次里程，保持当前节奏即可平稳度过整个规划周期</li><li>剩余里程充足，可适当增加出行频次，不用刻意省电省里程</li></ul><hr /><h2 id="%E4%B8%89%E3%80%81%F0%9F%93%8B-%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE%E6%B1%87%E6%80%BB%E6%80%BB%E8%A1%A8" tabindex="-1">三、📋 核心数据汇总总表</h2><table><thead><tr><th>项目</th><th>数值</th><th>与v3.0版本对比</th></tr></thead><tbody><tr><td>🗓️ 规划总周期</td><td>2026-02-11 至 2026-10-13（244天）</td><td>不变</td></tr><tr><td>💰 周期内可用总里程</td><td>24150km</td><td>不变</td></tr><tr><td>🚶 累计已行驶里程</td><td>16753km</td><td>+5790km</td></tr><tr><td>📦 剩余可用总里程</td><td>7397km</td><td>-5790km</td></tr><tr><td>💼 规划工作日日均里程</td><td>75km/天</td><td>不变</td></tr><tr><td>🎉 实际周末日均里程</td><td>≈139.21km/天</td><td>-2.59km/天</td></tr><tr><td>⚡ 剩余周末日均可用里程</td><td>≈218.94km/天</td><td>+43.6km/天</td></tr><tr><td>🎯 预计最终剩余自由里程</td><td>≈1355km</td><td>+181km</td></tr><tr><td>🔋 电池当前健康度推算</td><td>96% ~ 97%</td><td>正常自然衰减</td></tr></tbody></table><hr /><p>✨ <strong>最终总结</strong><br />无论是电池健康状态还是里程使用节奏，当前都处于<strong>非常健康且宽松</strong>的水平。你优秀的充电用车习惯为电池寿命打下了很好的基础，里程方面也有充足的余量可以自由支配，放心正常使用即可。</p><hr /><h1 id="%E8%BD%A6%E8%BE%86%E9%87%8C%E7%A8%8B%E4%BD%BF%E7%94%A8%E5%88%86%E6%9E%90%E4%B8%8E%E8%A7%84%E5%88%92%E6%8A%A5%E5%91%8A-v3.0" tabindex="-1">车辆里程使用分析与规划报告 v3.0</h1><p><strong>更新截止日期</strong>：2026-06-05<br /><strong>当前累积里程</strong>：66813km<br /><strong>原报告基准</strong>：2026-04-23 v2.0版本</p><hr /><h2 id="%E4%B8%80%E3%80%81%E6%A0%B8%E5%BF%83%E8%83%8C%E6%99%AF%E4%B8%8E%E7%9B%AE%E6%A0%87" tabindex="-1">一、核心背景与目标</h2><p>本报告基于你从 <strong>2026-02-11</strong> 至 <strong>2026-10-13</strong> 的车辆使用规划，结合截至 <strong>2026-06-05</strong> 的最新实际行驶数据，对里程消耗情况进行全面复盘分析，并对后续使用进行预测规划，核心目标是在保障工作日通勤的前提下，合理分配周末/节假日的自由行驶里程，避免后期里程不足。</p><hr /><h2 id="%E4%BA%8C%E3%80%81%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E4%B8%8E%E8%A7%84%E5%88%92%E5%81%87%E8%AE%BE%EF%BC%88%E4%BF%9D%E6%8C%81%E4%B8%8D%E5%8F%98%EF%BC%89" tabindex="-1">二、基础数据与规划假设（保持不变）</h2><ol><li><strong>总时间范围</strong>：2026-02-11 至 2026-10-13，共 <strong>244天</strong></li><li><strong>工作日统计</strong>：该时段内工作日（周一至周五）共 <strong>174天</strong>，周末/节假日共 <strong>70天</strong></li><li><strong>规划假设</strong>：工作日日均行驶 <strong>75km</strong>，用于通勤等固定场景</li><li><strong>车辆总里程上限</strong>：80000km，2026-02-11 初始里程为 <strong>55850km</strong>，剩余可用总里程为 <strong>24150km</strong></li></ol><hr /><h2 id="%E4%B8%89%E3%80%81%E8%A7%84%E5%88%92%E5%B1%82%E9%9D%A2%E7%9A%84%E9%87%8C%E7%A8%8B%E5%88%86%E9%85%8D%EF%BC%88%E4%BF%9D%E6%8C%81%E4%B8%8D%E5%8F%98%EF%BC%89" tabindex="-1">三、规划层面的里程分配（保持不变）</h2><ol><li><p><strong>工作日必开总里程</strong>：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>174</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>13050</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">174 \text{天} \times 75 \text{km/天} = 13050 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">7</span><span class="mord">4</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">3</span><span class="mord">0</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p><strong>周末/节假日总可用里程</strong>：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>24150</mn><mtext>km</mtext><mo>−</mo><mn>13050</mn><mtext>km</mtext><mo>=</mo><mn>11100</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">24150 \text{km} - 13050 \text{km} = 11100 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mord">4</span><span class="mord">1</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">3</span><span class="mord">0</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">1</span><span class="mord">1</span><span class="mord">0</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li></ol><p>对应全周期周末/节假日日均可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>11100</mn><mtext>km</mtext></mrow><mrow><mn>70</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>158.57</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{11100 \text{km}}{70 \text{天}} \approx 158.57 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">7</span><span class="mord mtight">0</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mord mtight">1</span><span class="mord mtight">1</span><span class="mord mtight">0</span><span class="mord mtight">0</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">5</span><span class="mord">8</span><span class="mord">.</span><span class="mord">5</span><span class="mord">7</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p><hr /><h2 id="%E5%9B%9B%E3%80%81%E6%88%AA%E8%87%B32026-06-05%E7%9A%84%E5%AE%9E%E9%99%85%E8%A1%8C%E9%A9%B6%E5%A4%8D%E7%9B%98" tabindex="-1">四、截至2026-06-05的实际行驶复盘</h2><h3 id="1.-%E6%97%B6%E9%97%B4%E4%B8%8E%E5%A4%A9%E6%95%B0%E7%BB%9F%E8%AE%A1" tabindex="-1">1. 时间与天数统计</h3><ul><li>统计时段：2026-02-11 至 2026-06-05，共 <strong>115天</strong></li><li>已过工作日：<strong>80天</strong>（原4月23日累计51天，新增29天）</li><li>已过周末/节假日：<strong>35天</strong>（原4月23日累计21天，新增14天）</li></ul><h3 id="2.-%E5%AE%9E%E9%99%85%E8%A1%8C%E9%A9%B6%E9%87%8C%E7%A8%8B" tabindex="-1">2. 实际行驶里程</h3><ul><li>2026-06-05 实际里程：<strong>66813km</strong></li><li>累计已行驶里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>66813</mn><mtext>km</mtext><mo>−</mo><mn>55850</mn><mtext>km</mtext><mo>=</mo><mn>10963</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">66813 \text{km} - 55850 \text{km} = 10963 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">6</span><span class="mord">6</span><span class="mord">8</span><span class="mord">1</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">5</span><span class="mord">5</span><span class="mord">8</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">0</span><span class="mord">9</span><span class="mord">6</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span></span></span></span></li></ul><h3 id="3.-%E6%8C%89%E8%A7%84%E5%88%92%E5%81%87%E8%AE%BE%E7%9A%84%E5%AE%9E%E9%99%85%E6%B6%88%E8%80%97%E6%8B%86%E5%88%86" tabindex="-1">3. 按规划假设的实际消耗拆分</h3><ul><li><p>工作日按计划行驶：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>80</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>6000</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">80 \text{天} \times 75 \text{km/天} = 6000 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">8</span><span class="mord">0</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">6</span><span class="mord">0</span><span class="mord">0</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>周末/节假日实际消耗：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>10963</mn><mtext>km</mtext><mo>−</mo><mn>6000</mn><mtext>km</mtext><mo>=</mo><mn>4963</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">10963 \text{km} - 6000 \text{km} = 4963 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">0</span><span class="mord">9</span><span class="mord">6</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">6</span><span class="mord">0</span><span class="mord">0</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">4</span><span class="mord">9</span><span class="mord">6</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>周末/节假日日均实际里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>4963</mn><mtext>km</mtext></mrow><mrow><mn>35</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>141.80</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{4963 \text{km}}{35 \text{天}} \approx 141.80 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mord mtight">5</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span><span class="mord mtight">9</span><span class="mord mtight">6</span><span class="mord mtight">3</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">4</span><span class="mord">1</span><span class="mord">.</span><span class="mord">8</span><span class="mord">0</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li><li><p>整体日均实际里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>10963</mn><mtext>km</mtext></mrow><mrow><mn>115</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>95.33</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{10963 \text{km}}{115 \text{天}} \approx 95.33 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mord mtight">1</span><span class="mord mtight">5</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mord mtight">0</span><span class="mord mtight">9</span><span class="mord mtight">6</span><span class="mord mtight">3</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">9</span><span class="mord">5</span><span class="mord">.</span><span class="mord">3</span><span class="mord">3</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li></ul><h3 id="4.-%E5%A4%8D%E7%9B%98%E7%BB%93%E8%AE%BA" tabindex="-1">4. 复盘结论</h3><ul><li>工作日里程消耗仍与规划完全一致，日均75km，通勤需求稳定保障。</li><li>周末/节假日日均消耗从4月23日的<strong>161.29km/天</strong>大幅下降至<strong>141.80km/天</strong>，已<strong>低于全周期规划日均158.57km</strong>，使用节奏从&quot;略超规划&quot;转为&quot;大幅宽松&quot;。</li><li>4月24日至6月5日期间，周末/节假日实际消耗里程为1576km（4963-3387），日均约112.57km，仅为前期（3月10日-4月23日）日均224.08km的50.2%，主要是因为五一假期后短途出行频率显著降低。</li></ul><hr /><h2 id="%E4%BA%94%E3%80%81%E5%89%A9%E4%BD%99%E9%87%8C%E7%A8%8B%E4%B8%8E%E5%90%8E%E7%BB%AD%E4%BD%BF%E7%94%A8%E9%A2%84%E6%B5%8B" tabindex="-1">五、剩余里程与后续使用预测</h2><h3 id="1.-%E5%89%A9%E4%BD%99%E5%8F%AF%E7%94%A8%E9%87%8C%E7%A8%8B" tabindex="-1">1. 剩余可用里程</h3><ul><li>总剩余规划里程：24150km</li><li>累计已消耗里程：10963km</li><li>剩余可用总里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>24150</mn><mtext>km</mtext><mo>−</mo><mn>10963</mn><mtext>km</mtext><mo>=</mo><mn>13187</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">24150 \text{km} - 10963 \text{km} = 13187 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mord">4</span><span class="mord">1</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">0</span><span class="mord">9</span><span class="mord">6</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">3</span><span class="mord">1</span><span class="mord">8</span><span class="mord">7</span><span class="mord text"><span class="mord">km</span></span></span></span></span></li></ul><h3 id="2.-%E5%89%A9%E4%BD%99%E6%97%B6%E9%97%B4%E4%B8%8E%E5%A4%A9%E6%95%B0" tabindex="-1">2. 剩余时间与天数</h3><ul><li>剩余时间：2026-06-06 至 2026-10-13，共 <strong>129天</strong></li><li>剩余工作日：174天 - 80天 = <strong>94天</strong></li><li>剩余周末/节假日：70天 - 35天 = <strong>35天</strong></li></ul><h3 id="3.-%E6%8C%89%E5%8E%9F%E8%A7%84%E5%88%92%E7%9A%84%E5%89%A9%E4%BD%99%E5%88%86%E9%85%8D" tabindex="-1">3. 按原规划的剩余分配</h3><ul><li><p>剩余工作日必开里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>94</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>7050</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">94 \text{天} \times 75 \text{km/天} = 7050 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">9</span><span class="mord">4</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">7</span><span class="mord">0</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>剩余周末/节假日可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>13187</mn><mtext>km</mtext><mo>−</mo><mn>7050</mn><mtext>km</mtext><mo>=</mo><mn>6137</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">13187 \text{km} - 7050 \text{km} = 6137 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">3</span><span class="mord">1</span><span class="mord">8</span><span class="mord">7</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">7</span><span class="mord">0</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">6</span><span class="mord">1</span><span class="mord">3</span><span class="mord">7</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>剩余周末/节假日日均可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>6137</mn><mtext>km</mtext></mrow><mrow><mn>35</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>175.34</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{6137 \text{km}}{35 \text{天}} \approx 175.34 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mord mtight">5</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">6</span><span class="mord mtight">1</span><span class="mord mtight">3</span><span class="mord mtight">7</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">7</span><span class="mord">5</span><span class="mord">.</span><span class="mord">3</span><span class="mord">4</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li></ul><h3 id="4.-%E6%8C%89%E5%BD%93%E5%89%8D%E5%AE%9E%E9%99%85%E8%8A%82%E5%A5%8F%E7%9A%84%E9%A2%84%E6%B5%8B" tabindex="-1">4. 按当前实际节奏的预测</h3><ul><li><p>若保持当前周末/节假日141.80km/天的消耗节奏：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>35</mn><mtext>天</mtext><mo>×</mo><mn>141.80</mn><mtext>km/天</mtext><mo>≈</mo><mn>4963</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">35 \text{天} \times 141.80 \text{km/天} \approx 4963 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">3</span><span class="mord">5</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">4</span><span class="mord">1</span><span class="mord">.</span><span class="mord">8</span><span class="mord">0</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">4</span><span class="mord">9</span><span class="mord">6</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>最终预计剩余里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>6137</mn><mtext>km</mtext><mo>−</mo><mn>4963</mn><mtext>km</mtext><mo>≈</mo><mn>1174</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">6137 \text{km} - 4963 \text{km} \approx 1174 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">6</span><span class="mord">1</span><span class="mord">3</span><span class="mord">7</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">4</span><span class="mord">9</span><span class="mord">6</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">1</span><span class="mord">7</span><span class="mord">4</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li></ul><p>按当前使用节奏，到2026年10月13日规划结束时，将剩余约1174km自由行驶里程，里程压力完全解除。</p><hr /><h2 id="%E5%85%AD%E3%80%81%E5%85%B3%E9%94%AE%E7%BB%93%E8%AE%BA%E4%B8%8E%E5%BB%BA%E8%AE%AE" tabindex="-1">六、关键结论与建议</h2><ol><li><strong>使用节奏已大幅宽松</strong>：截至6月5日，周末/节假日日均消耗已降至141.80km，远低于全周期规划值158.57km，且剩余时段的日均可用里程（175.34km）显著高于当前实际消耗，里程风险已完全消除。</li><li><strong>剩余里程非常充足</strong>：按当前节奏行驶，预计最终将剩余约1174km自由行驶里程，可用于安排长途自驾游或增加日常出行。</li><li><strong>优化建议</strong>：<ul><li>剩余周末/节假日日均可用里程提升至175km，比当前实际消耗多约33km/天，可放心安排更多出行活动。</li><li>若有长途自驾游计划（单次超过500km），无需提前压缩后续出行，剩余里程完全足够覆盖。</li><li>建议仍在每月底复盘一次里程消耗情况，确保使用节奏符合预期。</li></ul></li></ol><hr /><h2 id="%E4%B8%83%E3%80%81%E6%95%B0%E6%8D%AE%E6%B1%87%E6%80%BB%E8%A1%A8%EF%BC%88%E6%9B%B4%E6%96%B0%E7%89%88%EF%BC%89" tabindex="-1">七、数据汇总表（更新版）</h2><table><thead><tr><th>项目</th><th>数值</th><th>与4月23日v2.0版本对比</th></tr></thead><tbody><tr><td>总规划时间</td><td>2026-02-11 至 2026-10-13（244天）</td><td>不变</td></tr><tr><td>总剩余可用里程（初始）</td><td>24150km</td><td>不变</td></tr><tr><td>累计已消耗里程（截至6月5日）</td><td>10963km</td><td>+3751km</td></tr><tr><td>剩余可用总里程</td><td>13187km</td><td>-3751km</td></tr><tr><td>规划工作日日均里程</td><td>75km/天</td><td>不变</td></tr><tr><td>实际周末/节假日日均里程</td><td>≈141.80km/天</td><td>-19.49km/天</td></tr><tr><td>剩余周末/节假日日均可用里程</td><td>≈175.34km/天</td><td>+17.93km/天</td></tr><tr><td>预计最终里程偏差</td><td>≈+1174km（剩余）</td><td>从-190.21km（超支）转为大幅剩余</td></tr></tbody></table><hr /><h1 id="%E8%BD%A6%E8%BE%86%E9%87%8C%E7%A8%8B%E4%BD%BF%E7%94%A8%E5%88%86%E6%9E%90%E4%B8%8E%E8%A7%84%E5%88%92%E6%8A%A5%E5%91%8A-v2.0" tabindex="-1">车辆里程使用分析与规划报告 v2.0</h1><p><strong>更新截止日期</strong>：2026-04-23<br /><strong>当前累积里程</strong>：63062km<br /><strong>原报告基准</strong>：2026-03-09 v1.0版本</p><hr /><h2 id="%E4%B8%80%E3%80%81%E6%A0%B8%E5%BF%83%E8%83%8C%E6%99%AF%E4%B8%8E%E7%9B%AE%E6%A0%87-1" tabindex="-1">一、核心背景与目标</h2><p>本报告基于你从 <strong>2026-02-11</strong> 至 <strong>2026-10-13</strong> 的车辆使用规划，结合截至 <strong>2026-04-23</strong> 的最新实际行驶数据，对里程消耗情况进行复盘分析，并对后续使用进行预测规划，核心目标是在保障工作日通勤的前提下，合理分配周末/节假日的自由行驶里程，避免后期里程不足。</p><hr /><h2 id="%E4%BA%8C%E3%80%81%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E4%B8%8E%E8%A7%84%E5%88%92%E5%81%87%E8%AE%BE%EF%BC%88%E4%BF%9D%E6%8C%81%E4%B8%8D%E5%8F%98%EF%BC%89-1" tabindex="-1">二、基础数据与规划假设（保持不变）</h2><ol><li><strong>总时间范围</strong>：2026-02-11 至 2026-10-13，共 <strong>244天</strong></li><li><strong>工作日统计</strong>：该时段内工作日（周一至周五）共 <strong>174天</strong>，周末/节假日共 <strong>70天</strong></li><li><strong>规划假设</strong>：工作日日均行驶 <strong>75km</strong>，用于通勤等固定场景</li><li><strong>车辆总里程上限</strong>：80000km，2026-02-11 初始里程为 <strong>55850km</strong>，剩余可用总里程为 <strong>24150km</strong></li></ol><hr /><h2 id="%E4%B8%89%E3%80%81%E8%A7%84%E5%88%92%E5%B1%82%E9%9D%A2%E7%9A%84%E9%87%8C%E7%A8%8B%E5%88%86%E9%85%8D%EF%BC%88%E4%BF%9D%E6%8C%81%E4%B8%8D%E5%8F%98%EF%BC%89-1" tabindex="-1">三、规划层面的里程分配（保持不变）</h2><ol><li><p><strong>工作日必开总里程</strong>：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>174</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>13050</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">174 \text{天} \times 75 \text{km/天} = 13050 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">7</span><span class="mord">4</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">3</span><span class="mord">0</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p><strong>周末/节假日总可用里程</strong>：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>24150</mn><mtext>km</mtext><mo>−</mo><mn>13050</mn><mtext>km</mtext><mo>=</mo><mn>11100</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">24150 \text{km} - 13050 \text{km} = 11100 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mord">4</span><span class="mord">1</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">3</span><span class="mord">0</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">1</span><span class="mord">1</span><span class="mord">0</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li></ol><p>对应全周期周末/节假日日均可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>11100</mn><mtext>km</mtext></mrow><mrow><mn>70</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>158.57</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{11100 \text{km}}{70 \text{天}} \approx 158.57 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">7</span><span class="mord mtight">0</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mord mtight">1</span><span class="mord mtight">1</span><span class="mord mtight">0</span><span class="mord mtight">0</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">5</span><span class="mord">8</span><span class="mord">.</span><span class="mord">5</span><span class="mord">7</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p><hr /><h2 id="%E5%9B%9B%E3%80%81%E6%88%AA%E8%87%B32026-04-23%E7%9A%84%E5%AE%9E%E9%99%85%E8%A1%8C%E9%A9%B6%E5%A4%8D%E7%9B%98" tabindex="-1">四、截至2026-04-23的实际行驶复盘</h2><h3 id="1.-%E6%97%B6%E9%97%B4%E4%B8%8E%E5%A4%A9%E6%95%B0%E7%BB%9F%E8%AE%A1-1" tabindex="-1">1. 时间与天数统计</h3><ul><li>统计时段：2026-02-11 至 2026-04-23，共 <strong>72天</strong></li><li>已过工作日：<strong>51天</strong>（原3月9日累计19天，新增32天）</li><li>已过周末/节假日：<strong>21天</strong>（原3月9日累计8天，新增13天）</li></ul><h3 id="2.-%E5%AE%9E%E9%99%85%E8%A1%8C%E9%A9%B6%E9%87%8C%E7%A8%8B-1" tabindex="-1">2. 实际行驶里程</h3><ul><li>2026-04-23 实际里程：<strong>63062km</strong></li><li>累计已行驶里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>63062</mn><mtext>km</mtext><mo>−</mo><mn>55850</mn><mtext>km</mtext><mo>=</mo><mn>7212</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">63062 \text{km} - 55850 \text{km} = 7212 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">6</span><span class="mord">3</span><span class="mord">0</span><span class="mord">6</span><span class="mord">2</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">5</span><span class="mord">5</span><span class="mord">8</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">7</span><span class="mord">2</span><span class="mord">1</span><span class="mord">2</span><span class="mord text"><span class="mord">km</span></span></span></span></span></li></ul><h3 id="3.-%E6%8C%89%E8%A7%84%E5%88%92%E5%81%87%E8%AE%BE%E7%9A%84%E5%AE%9E%E9%99%85%E6%B6%88%E8%80%97%E6%8B%86%E5%88%86-1" tabindex="-1">3. 按规划假设的实际消耗拆分</h3><ul><li><p>工作日按计划行驶：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>51</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>3825</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">51 \text{天} \times 75 \text{km/天} = 3825 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">5</span><span class="mord">1</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">3</span><span class="mord">8</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>周末/节假日实际消耗：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>7212</mn><mtext>km</mtext><mo>−</mo><mn>3825</mn><mtext>km</mtext><mo>=</mo><mn>3387</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">7212 \text{km} - 3825 \text{km} = 3387 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">7</span><span class="mord">2</span><span class="mord">1</span><span class="mord">2</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">3</span><span class="mord">8</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">3</span><span class="mord">3</span><span class="mord">8</span><span class="mord">7</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>周末/节假日日均实际里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>3387</mn><mtext>km</mtext></mrow><mrow><mn>21</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>161.29</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{3387 \text{km}}{21 \text{天}} \approx 161.29 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mtight">1</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mord mtight">3</span><span class="mord mtight">8</span><span class="mord mtight">7</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">6</span><span class="mord">1</span><span class="mord">.</span><span class="mord">2</span><span class="mord">9</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li><li><p>整体日均实际里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>7212</mn><mtext>km</mtext></mrow><mrow><mn>72</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>100.17</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{7212 \text{km}}{72 \text{天}} \approx 100.17 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">7</span><span class="mord mtight">2</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">7</span><span class="mord mtight">2</span><span class="mord mtight">1</span><span class="mord mtight">2</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">0</span><span class="mord">0</span><span class="mord">.</span><span class="mord">1</span><span class="mord">7</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li></ul><h3 id="4.-%E5%A4%8D%E7%9B%98%E7%BB%93%E8%AE%BA-1" tabindex="-1">4. 复盘结论</h3><ul><li>工作日里程消耗仍与规划完全一致，日均75km，通勤需求稳定保障。</li><li>周末/节假日日均消耗从3月9日的<strong>59.25km/天</strong>大幅上升至<strong>161.29km/天</strong>，已<strong>超过全周期规划日均158.57km</strong>，使用节奏从&quot;宽松&quot;转为&quot;略超规划&quot;。</li><li>3月10日至4月23日期间，周末/节假日实际消耗里程为2913km（3387-474），日均约224.08km，是前期节奏的3.78倍，主要由近期短途出行增加导致。</li></ul><hr /><h2 id="%E4%BA%94%E3%80%81%E5%89%A9%E4%BD%99%E9%87%8C%E7%A8%8B%E4%B8%8E%E5%90%8E%E7%BB%AD%E4%BD%BF%E7%94%A8%E9%A2%84%E6%B5%8B-1" tabindex="-1">五、剩余里程与后续使用预测</h2><h3 id="1.-%E5%89%A9%E4%BD%99%E5%8F%AF%E7%94%A8%E9%87%8C%E7%A8%8B-1" tabindex="-1">1. 剩余可用里程</h3><ul><li>总剩余规划里程：24150km</li><li>累计已消耗里程：7212km</li><li>剩余可用总里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>24150</mn><mtext>km</mtext><mo>−</mo><mn>7212</mn><mtext>km</mtext><mo>=</mo><mn>16938</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">24150 \text{km} - 7212 \text{km} = 16938 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mord">4</span><span class="mord">1</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">7</span><span class="mord">2</span><span class="mord">1</span><span class="mord">2</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">6</span><span class="mord">9</span><span class="mord">3</span><span class="mord">8</span><span class="mord text"><span class="mord">km</span></span></span></span></span></li></ul><h3 id="2.-%E5%89%A9%E4%BD%99%E6%97%B6%E9%97%B4%E4%B8%8E%E5%A4%A9%E6%95%B0-1" tabindex="-1">2. 剩余时间与天数</h3><ul><li>剩余时间：2026-04-24 至 2026-10-13，共 <strong>172天</strong></li><li>剩余工作日：174天 - 51天 = <strong>123天</strong></li><li>剩余周末/节假日：70天 - 21天 = <strong>49天</strong></li></ul><h3 id="3.-%E6%8C%89%E5%8E%9F%E8%A7%84%E5%88%92%E7%9A%84%E5%89%A9%E4%BD%99%E5%88%86%E9%85%8D-1" tabindex="-1">3. 按原规划的剩余分配</h3><ul><li><p>剩余工作日必开里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>123</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>9225</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">123 \text{天} \times 75 \text{km/天} = 9225 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">2</span><span class="mord">3</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">9</span><span class="mord">2</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>剩余周末/节假日可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>16938</mn><mtext>km</mtext><mo>−</mo><mn>9225</mn><mtext>km</mtext><mo>=</mo><mn>7713</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">16938 \text{km} - 9225 \text{km} = 7713 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">6</span><span class="mord">9</span><span class="mord">3</span><span class="mord">8</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">9</span><span class="mord">2</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">7</span><span class="mord">7</span><span class="mord">1</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>剩余周末/节假日日均可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>7713</mn><mtext>km</mtext></mrow><mrow><mn>49</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>157.41</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{7713 \text{km}}{49 \text{天}} \approx 157.41 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span><span class="mord mtight">9</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">7</span><span class="mord mtight">7</span><span class="mord mtight">1</span><span class="mord mtight">3</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">5</span><span class="mord">7</span><span class="mord">.</span><span class="mord">4</span><span class="mord">1</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li></ul><h3 id="4.-%E6%8C%89%E5%BD%93%E5%89%8D%E5%AE%9E%E9%99%85%E8%8A%82%E5%A5%8F%E7%9A%84%E9%A2%84%E6%B5%8B-1" tabindex="-1">4. 按当前实际节奏的预测</h3><ul><li><p>若保持当前周末/节假日161.29km/天的消耗节奏：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>49</mn><mtext>天</mtext><mo>×</mo><mn>161.29</mn><mtext>km/天</mtext><mo>≈</mo><mn>7903.21</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">49 \text{天} \times 161.29 \text{km/天} \approx 7903.21 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">4</span><span class="mord">9</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">6</span><span class="mord">1</span><span class="mord">.</span><span class="mord">2</span><span class="mord">9</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">7</span><span class="mord">9</span><span class="mord">0</span><span class="mord">3</span><span class="mord">.</span><span class="mord">2</span><span class="mord">1</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>最终预计超支里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>7903.21</mn><mtext>km</mtext><mo>−</mo><mn>7713</mn><mtext>km</mtext><mo>≈</mo><mn>190.21</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">7903.21 \text{km} - 7713 \text{km} \approx 190.21 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">7</span><span class="mord">9</span><span class="mord">0</span><span class="mord">3</span><span class="mord">.</span><span class="mord">2</span><span class="mord">1</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">7</span><span class="mord">7</span><span class="mord">1</span><span class="mord">3</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">9</span><span class="mord">0</span><span class="mord">.</span><span class="mord">2</span><span class="mord">1</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li></ul><p>若不调整使用节奏，预计在2026年10月初将提前耗尽自由行驶里程，需压缩工作日通勤或提前规划备用方案。</p><hr /><h2 id="%E5%85%AD%E3%80%81%E5%85%B3%E9%94%AE%E7%BB%93%E8%AE%BA%E4%B8%8E%E5%BB%BA%E8%AE%AE-1" tabindex="-1">六、关键结论与建议</h2><ol><li><strong>使用节奏已超规划</strong>：截至4月23日，周末/节假日日均消耗已超过全周期规划值，且剩余时段的日均可用里程（157.41km）略低于当前实际消耗（161.29km），存在小幅超支风险。</li><li><strong>超支幅度可控</strong>：预计最终超支约190km，幅度较小，通过小幅调整即可避免里程不足。</li><li><strong>优化建议</strong>：<ul><li>建议将剩余周末/节假日日均里程控制在<strong>157km以内</strong>，平均每天仅需减少约4km即可完全避免超支。</li><li>若有长途自驾游计划（单次超过300km），可提前预留对应里程，通过减少后续1-2个周末的短途出行来平衡。</li><li>建议每月底复盘一次里程消耗情况，及时调整下月使用计划，避免后期被动压缩。</li></ul></li></ol><hr /><h2 id="%E4%B8%83%E3%80%81%E6%95%B0%E6%8D%AE%E6%B1%87%E6%80%BB%E8%A1%A8%EF%BC%88%E6%9B%B4%E6%96%B0%E7%89%88%EF%BC%89-1" tabindex="-1">七、数据汇总表（更新版）</h2><table><thead><tr><th>项目</th><th>数值</th><th>与3月9日版本对比</th></tr></thead><tbody><tr><td>总规划时间</td><td>2026-02-11 至 2026-10-13（244天）</td><td>不变</td></tr><tr><td>总剩余可用里程（初始）</td><td>24150km</td><td>不变</td></tr><tr><td>累计已消耗里程（截至4月23日）</td><td>7212km</td><td>+5313km</td></tr><tr><td>剩余可用总里程</td><td>16938km</td><td>-5313km</td></tr><tr><td>规划工作日日均里程</td><td>75km/天</td><td>不变</td></tr><tr><td>实际周末/节假日日均里程</td><td>≈161.29km/天</td><td>+102.04km/天</td></tr><tr><td>剩余周末/节假日日均可用里程</td><td>≈157.41km/天</td><td>-13.98km/天</td></tr><tr><td>预计最终里程偏差</td><td>≈-190.21km（超支）</td><td>从+6952.5km转为超支</td></tr></tbody></table><hr /><h1 id="%E8%BD%A6%E8%BE%86%E9%87%8C%E7%A8%8B%E4%BD%BF%E7%94%A8%E5%88%86%E6%9E%90%E4%B8%8E%E8%A7%84%E5%88%92%E6%8A%A5%E5%91%8A-v1.0" tabindex="-1">车辆里程使用分析与规划报告 v1.0</h1><h2 id="%E4%B8%80%E3%80%81%E6%A0%B8%E5%BF%83%E8%83%8C%E6%99%AF%E4%B8%8E%E7%9B%AE%E6%A0%87-2" tabindex="-1">一、核心背景与目标</h2><p>本报告基于你从 <strong>2026-02-11</strong> 至 <strong>2026-10-13</strong> 的车辆使用规划，结合截至 <strong>2026-03-09</strong> 的实际行驶数据，对里程消耗情况进行复盘分析，并对后续使用进行预测规划，核心目标是在保障工作日通勤的前提下，合理分配周末/节假日的自由行驶里程。</p><hr /><h2 id="%E4%BA%8C%E3%80%81%E5%9F%BA%E7%A1%80%E6%95%B0%E6%8D%AE%E4%B8%8E%E8%A7%84%E5%88%92%E5%81%87%E8%AE%BE" tabindex="-1">二、基础数据与规划假设</h2><ol><li><strong>总时间范围</strong>：2026-02-11 至 2026-10-13，共 <strong>244天</strong></li><li><strong>工作日统计</strong>：该时段内工作日（周一至周五）共 <strong>174天</strong>，周末/节假日共 <strong>70天</strong></li><li><strong>规划假设</strong>：工作日日均行驶 <strong>75km</strong>，用于通勤等固定场景</li><li><strong>车辆总里程上限</strong>：80000km，2026-02-11 初始里程为 <strong>55850km</strong>，剩余可用总里程为 <strong>24150km</strong></li></ol><hr /><h2 id="%E4%B8%89%E3%80%81%E8%A7%84%E5%88%92%E5%B1%82%E9%9D%A2%E7%9A%84%E9%87%8C%E7%A8%8B%E5%88%86%E9%85%8D" tabindex="-1">三、规划层面的里程分配</h2><ol><li><p><strong>工作日必开里程</strong>：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>174</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>13050</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">174 \text{天} \times 75 \text{km/天} = 13050 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">7</span><span class="mord">4</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">3</span><span class="mord">0</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p><strong>周末/节假日可用里程</strong>：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>24150</mn><mtext>km</mtext><mo>−</mo><mn>13050</mn><mtext>km</mtext><mo>=</mo><mn>11100</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">24150 \text{km} - 13050 \text{km} = 11100 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mord">4</span><span class="mord">1</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">3</span><span class="mord">0</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">1</span><span class="mord">1</span><span class="mord">0</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li></ol><p>对应周末/节假日日均可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>11100</mn><mtext>km</mtext></mrow><mrow><mn>70</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>158.57</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{11100 \text{km}}{70 \text{天}} \approx 158.57 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">7</span><span class="mord mtight">0</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mord mtight">1</span><span class="mord mtight">1</span><span class="mord mtight">0</span><span class="mord mtight">0</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">5</span><span class="mord">8</span><span class="mord">.</span><span class="mord">5</span><span class="mord">7</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p><hr /><h2 id="%E5%9B%9B%E3%80%81%E6%88%AA%E8%87%B32026-03-09%E7%9A%84%E5%AE%9E%E9%99%85%E8%A1%8C%E9%A9%B6%E5%A4%8D%E7%9B%98" tabindex="-1">四、截至2026-03-09的实际行驶复盘</h2><h3 id="1.-%E6%97%B6%E9%97%B4%E4%B8%8E%E5%A4%A9%E6%95%B0%E7%BB%9F%E8%AE%A1-2" tabindex="-1">1. 时间与天数统计</h3><ul><li>统计时段：2026-02-11 至 2026-03-09，共 <strong>27天</strong></li><li>工作日：<strong>19天</strong>，周末/节假日：<strong>8天</strong></li></ul><h3 id="2.-%E5%AE%9E%E9%99%85%E8%A1%8C%E9%A9%B6%E9%87%8C%E7%A8%8B-2" tabindex="-1">2. 实际行驶里程</h3><ul><li>2026-03-09 实际里程：<strong>57749km</strong></li><li>已行驶里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>57749</mn><mtext>km</mtext><mo>−</mo><mn>55850</mn><mtext>km</mtext><mo>=</mo><mn>1899</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">57749 \text{km} - 55850 \text{km} = 1899 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">5</span><span class="mord">7</span><span class="mord">7</span><span class="mord">4</span><span class="mord">9</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">5</span><span class="mord">5</span><span class="mord">8</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">8</span><span class="mord">9</span><span class="mord">9</span><span class="mord text"><span class="mord">km</span></span></span></span></span></li></ul><h3 id="3.-%E6%8C%89%E8%A7%84%E5%88%92%E5%81%87%E8%AE%BE%E7%9A%84%E5%AE%9E%E9%99%85%E6%B6%88%E8%80%97%E6%8B%86%E5%88%86-2" tabindex="-1">3. 按规划假设的实际消耗拆分</h3><ul><li><p>工作日按计划行驶：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>19</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>1425</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">19 \text{天} \times 75 \text{km/天} = 1425 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">9</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">4</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>周末/节假日实际消耗：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1899</mn><mtext>km</mtext><mo>−</mo><mn>1425</mn><mtext>km</mtext><mo>=</mo><mn>474</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">1899 \text{km} - 1425 \text{km} = 474 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">8</span><span class="mord">9</span><span class="mord">9</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">4</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">4</span><span class="mord">7</span><span class="mord">4</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>周末/节假日日均实际里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>474</mn><mtext>km</mtext></mrow><mrow><mn>8</mn><mtext>天</mtext></mrow></mfrac><mo>=</mo><mn>59.25</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{474 \text{km}}{8 \text{天}} = 59.25 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">8</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span><span class="mord mtight">7</span><span class="mord mtight">4</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">5</span><span class="mord">9</span><span class="mord">.</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li><li><p>整体日均实际里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>1899</mn><mtext>km</mtext></mrow><mrow><mn>27</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>70.33</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{1899 \text{km}}{27 \text{天}} \approx 70.33 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mtight">7</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mord mtight">8</span><span class="mord mtight">9</span><span class="mord mtight">9</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">0</span><span class="mord">.</span><span class="mord">3</span><span class="mord">3</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li></ul><h3 id="4.-%E5%A4%8D%E7%9B%98%E7%BB%93%E8%AE%BA-2" tabindex="-1">4. 复盘结论</h3><ul><li>工作日里程消耗与规划完全一致，日均75km，保障了通勤需求。</li><li>周末/节假日日均消耗59.25km，远低于规划的158.57km/天，<strong>实际使用节奏远低于预期</strong>，为后续预留了充足的自由行驶空间。</li></ul><hr /><h2 id="%E4%BA%94%E3%80%81%E5%89%A9%E4%BD%99%E9%87%8C%E7%A8%8B%E4%B8%8E%E5%90%8E%E7%BB%AD%E4%BD%BF%E7%94%A8%E9%A2%84%E6%B5%8B-2" tabindex="-1">五、剩余里程与后续使用预测</h2><h3 id="1.-%E5%89%A9%E4%BD%99%E5%8F%AF%E7%94%A8%E9%87%8C%E7%A8%8B-2" tabindex="-1">1. 剩余可用里程</h3><ul><li>总剩余规划里程：24150km</li><li>已消耗里程：1899km</li><li>剩余可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>24150</mn><mtext>km</mtext><mo>−</mo><mn>1899</mn><mtext>km</mtext><mo>=</mo><mn>22251</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">24150 \text{km} - 1899 \text{km} = 22251 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mord">4</span><span class="mord">1</span><span class="mord">5</span><span class="mord">0</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">8</span><span class="mord">9</span><span class="mord">9</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">2</span><span class="mord">2</span><span class="mord">2</span><span class="mord">5</span><span class="mord">1</span><span class="mord text"><span class="mord">km</span></span></span></span></span></li></ul><h3 id="2.-%E5%89%A9%E4%BD%99%E6%97%B6%E9%97%B4%E4%B8%8E%E5%A4%A9%E6%95%B0-2" tabindex="-1">2. 剩余时间与天数</h3><ul><li>剩余时间：2026-03-10 至 2026-10-13，共 <strong>217天</strong></li><li>剩余工作日：174天 - 19天 = <strong>155天</strong></li><li>剩余周末/节假日：70天 - 8天 = <strong>62天</strong></li></ul><h3 id="3.-%E6%8C%89%E5%8E%9F%E8%A7%84%E5%88%92%E7%9A%84%E5%89%A9%E4%BD%99%E5%88%86%E9%85%8D-2" tabindex="-1">3. 按原规划的剩余分配</h3><ul><li><p>剩余工作日必开里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>155</mn><mtext>天</mtext><mo>×</mo><mn>75</mn><mtext>km/天</mtext><mo>=</mo><mn>11625</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">155 \text{天} \times 75 \text{km/天} = 11625 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">5</span><span class="mord">5</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">7</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">1</span><span class="mord">6</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>剩余周末/节假日可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>22251</mn><mtext>km</mtext><mo>−</mo><mn>11625</mn><mtext>km</mtext><mo>=</mo><mn>10626</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">22251 \text{km} - 11625 \text{km} = 10626 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">2</span><span class="mord">2</span><span class="mord">2</span><span class="mord">5</span><span class="mord">1</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">1</span><span class="mord">6</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">1</span><span class="mord">0</span><span class="mord">6</span><span class="mord">2</span><span class="mord">6</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>剩余周末/节假日日均可用里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mn>10626</mn><mtext>km</mtext></mrow><mrow><mn>62</mn><mtext>天</mtext></mrow></mfrac><mo>≈</mo><mn>171.39</mn><mtext>km/天</mtext></mrow><annotation encoding="application/x-tex">\frac{10626 \text{km}}{62 \text{天}} \approx 171.39 \text{km/天}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2251079999999999em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8801079999999999em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">6</span><span class="mord mtight">2</span><span class="mord text mtight"><span class="mord cjk_fallback mtight">天</span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mord mtight">0</span><span class="mord mtight">6</span><span class="mord mtight">2</span><span class="mord mtight">6</span><span class="mord text mtight"><span class="mord mtight">km</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mord">7</span><span class="mord">1</span><span class="mord">.</span><span class="mord">3</span><span class="mord">9</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span></span></span></span></p></li></ul><h3 id="4.-%E6%8C%89%E5%BD%93%E5%89%8D%E5%AE%9E%E9%99%85%E8%8A%82%E5%A5%8F%E7%9A%84%E9%A2%84%E6%B5%8B-2" tabindex="-1">4. 按当前实际节奏的预测</h3><ul><li><p>若保持当前周末/节假日59.25km/天的消耗节奏：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>62</mn><mtext>天</mtext><mo>×</mo><mn>59.25</mn><mtext>km/天</mtext><mo>=</mo><mn>3673.5</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">62 \text{天} \times 59.25 \text{km/天} = 3673.5 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord">6</span><span class="mord">2</span><span class="mord text"><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">5</span><span class="mord">9</span><span class="mord">.</span><span class="mord">2</span><span class="mord">5</span><span class="mord text"><span class="mord">km/</span><span class="mord cjk_fallback">天</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">3</span><span class="mord">6</span><span class="mord">7</span><span class="mord">3</span><span class="mord">.</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li><li><p>最终可节省的自由里程：<br /><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>10626</mn><mtext>km</mtext><mo>−</mo><mn>3673.5</mn><mtext>km</mtext><mo>=</mo><mn>6952.5</mn><mtext>km</mtext></mrow><annotation encoding="application/x-tex">10626 \text{km} - 3673.5 \text{km} = 6952.5 \text{km}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mord">0</span><span class="mord">6</span><span class="mord">2</span><span class="mord">6</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">3</span><span class="mord">6</span><span class="mord">7</span><span class="mord">3</span><span class="mord">.</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord">6</span><span class="mord">9</span><span class="mord">5</span><span class="mord">2</span><span class="mord">.</span><span class="mord">5</span><span class="mord text"><span class="mord">km</span></span></span></span></span></p></li></ul><p>这部分里程可用于长途出行、临时需求等场景。</p><hr /><h2 id="%E5%85%AD%E3%80%81%E5%85%B3%E9%94%AE%E7%BB%93%E8%AE%BA%E4%B8%8E%E5%BB%BA%E8%AE%AE-2" tabindex="-1">六、关键结论与建议</h2><ol><li><strong>当前节奏健康</strong>：截至3月9日，你的里程消耗节奏远低于规划，尤其是周末/节假日的使用强度较低，整体处于“宽松使用”状态。</li><li><strong>后续空间充足</strong>：即使保持当前节奏，到10月13日仍可节省近7000km的自由里程，可灵活安排长途出行或应对突发需求。</li><li><strong>优化建议</strong>：<ul><li>若有长途出行计划（如自驾游），可优先使用这部分预留的自由里程，无需压缩工作日通勤。</li><li>若希望更精准控制，可每月初设定周末/节假日里程预算，避免后期被动压缩使用。</li></ul></li></ol><hr /><h2 id="%E4%B8%83%E3%80%81%E6%95%B0%E6%8D%AE%E6%B1%87%E6%80%BB%E8%A1%A8" tabindex="-1">七、数据汇总表</h2><table><thead><tr><th>项目</th><th>数值</th></tr></thead><tbody><tr><td>总规划时间</td><td>2026-02-11 至 2026-10-13（244天）</td></tr><tr><td>总剩余可用里程</td><td>24150km</td></tr><tr><td>已消耗里程（截至3月9日）</td><td>1899km</td></tr><tr><td>剩余可用里程</td><td>22251km</td></tr><tr><td>规划工作日日均里程</td><td>75km/天</td></tr><tr><td>实际周末/节假日日均里程</td><td>59.25km/天</td></tr><tr><td>剩余周末/节假日日均可用里程</td><td>≈171.39km/天</td></tr><tr><td>预计可节省自由里程</td><td>≈6952.5km</td></tr></tbody></table><hr />]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[FH6 日本地图完全指南]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/fh6-japan" />
                <id>tag:https://fx67ll.xyz,2026-08-06:fh6-japan</id>
                <published>2026-08-06T20:06:00+08:00</published>
                <updated>2026-08-07T11:44:20+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="fh6-%E6%97%A5%E6%9C%AC%E5%9C%B0%E5%9B%BE%E6%94%BB%E7%95%A5%E5%90%88%E9%9B%86%EF%BC%9A%E5%9C%B0%E6%A0%87-%2F-%E6%94%B9%E8%A3%85-%2F-%E6%B5%8B%E9%80%9F-%2F-%E6%BC%82%E7%A7%BB%E4%B8%80%E7%AF%87%E6%90%9E%E5%AE%9A" tabindex="-1">FH6 日本地图攻略合集：地标 / 改装 / 测速 / 漂移一篇搞定</h1><blockquote><p>最后更新：2026年6月23日</p><p>不是攻略，是我自己踩坑、查资料、攒出来的一篇随手笔记。<br />如果你也刚进日本地图、对着满屏的图标不知道先干啥，这篇可能帮得上忙。</p></blockquote><hr /><h2 id="%F0%9F%97%BE-%E5%85%88%E8%AF%B4%E8%BF%99%E4%BB%A3%E5%9C%B0%E5%9B%BE%EF%BC%9A%E6%97%A5%E6%9C%AC%EF%BC%8C%E7%9C%9F%E7%9A%84%E4%B8%8D%E4%B8%80%E6%A0%B7" tabindex="-1">🗾 先说这代地图：日本，真的不一样</h2><p>地平线6把舞台搬到了日本，这事儿我从官宣就开始盼。真上手之后，最大的感受是——<strong>这代地图的&quot;密度&quot;太高了</strong>。</p><p>从东京的霓虹高楼到志摩山的盘山公路，从白川乡的合掌村到最南端的航天发射基地，一张图里塞了至少七八种完全不同的地貌。而且因为日本本身地形狭长，地图纵向拉伸感很强，从最北的滑雪场一路开到最南的海边，那种&quot;穿越感&quot;是前几代没有的。</p><h3 id="%E5%9C%B0%E5%9B%BE%E5%A4%A7%E6%A6%82%E5%88%86%E8%BF%99%E4%B9%88%E5%87%A0%E5%9D%97-%F0%9F%97%BA%EF%B8%8F" tabindex="-1">地图大概分这么几块 🗺️</h3><table><thead><tr><th>区域</th><th>大概位置</th><th>特色</th></tr></thead><tbody><tr><td><strong>东京 Tokyo</strong></td><td>东南主城</td><td>高楼、高速立交、涩谷十字路口</td></tr><tr><td><strong>大谷 Ohtani</strong></td><td>中东部</td><td>地平线嘉年华主场、金阁寺、无线电塔</td></tr><tr><td><strong>志摩山 Shimanoyama</strong></td><td>中部</td><td>群山、湖泊、奈良井宿、盘山公路</td></tr><tr><td><strong>外山 Sotoyama</strong></td><td>最北部</td><td>滑雪场、雪地山道</td></tr><tr><td><strong>高城 Takashiro</strong></td><td>西北部</td><td>弘前城、樱花、深山瀑布</td></tr><tr><td><strong>南野 Minamino</strong></td><td>西南部</td><td>白川乡合掌屋、古寺、石桥</td></tr><tr><td><strong>伊东 Ito</strong></td><td>南部沿海</td><td>机场、千年古树、海边小镇</td></tr><tr><td><strong>南部秘境</strong></td><td>最南端</td><td>航天发射基地、火箭</td></tr></tbody></table><blockquote><p>💡 个人感受：如果你是山道党，志摩山和外山的山路会让你爽到；如果喜欢城市竞速，东京周边的高速网很过瘾；越野拉力的话，南野和伊东之间的山区有不少烂路可以探索。</p></blockquote><h3 id="%E5%AD%A3%E8%8A%82%E7%B3%BB%E7%BB%9F-%F0%9F%8C%B8" tabindex="-1">季节系统 🌸</h3><p>这代还是延续了地平线系列的四季轮换，每周换一个季节。但日本地图的季节感特别强——春天樱花飘、夏天祭典风、秋天红叶满山、冬天外山直接被雪埋了。</p><p><strong>季节不只是好看，还直接影响收集物</strong>，比如鲤鱼旗只有春天有，七夕短册夏天才刷。错过的话要么等一周，要么去蓝图里调季节。</p><hr /><h2 id="%F0%9F%93%B8-discover-japan%EF%BC%9A26%E5%A4%84%E5%9C%B0%E6%A0%87%E6%89%93%E5%8D%A1%E5%85%A8%E8%AE%B0%E5%BD%95" tabindex="-1">📸 Discover Japan：26处地标打卡全记录</h2><p>这是我进游戏后第一个想清完的收集项。26个地标分散在全图，每个首次拍照给100观光积分，积分解锁奖励。</p><p>我把它们分成了三类，方便你按图索骥：</p><h3 id="%F0%9F%8F%AF-%E5%9B%BA%E5%AE%9A%E5%AE%9E%E6%99%AF%E5%9C%B0%E6%A0%87%EF%BC%8813%E5%A4%84%EF%BC%8C%E5%85%A8%E5%B9%B4%E9%9A%8F%E6%97%B6%E5%8F%AF%E6%8B%8D%EF%BC%89" tabindex="-1">🏯 固定实景地标（13处，全年随时可拍）</h3><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><p><strong>志摩山</strong></p><ul><li>⑥ <strong>平和水上鸟居</strong>：地图中部湖泊湖心，拍照角度很出片</li></ul><p><strong>外山</strong></p><ul><li>⑦ <strong>外山滑雪场</strong>：地图最北侧的雪地建筑群</li></ul><p><strong>高城</strong></p><ul><li>⑧ <strong>弘前城</strong>：高城区域临水古城，樱花环绕的时候最美</li><li>⑨ <strong>那智瀑布寺</strong>：弘前城东侧深山里的瀑布旁</li></ul><p><strong>南野</strong></p><ul><li>⑩ <strong>白川乡合掌屋</strong>：南野北部山间古村落</li><li>⑪ <strong>琉璃光寺</strong>：南野西侧深山寺庙</li></ul><p><strong>伊东</strong></p><ul><li>⑫ <strong>千年大杉树</strong>：伊东机场北侧山林里的巨型古树</li></ul><p><strong>南部秘境</strong></p><ul><li>⑬ <strong>巨型航天火箭</strong>：地图最南端的航天发射基地</li></ul><h3 id="%F0%9F%8E%A8-%E8%A1%97%E5%A4%B4%E8%89%BA%E6%9C%AF%E5%A3%81%E7%94%BB%EF%BC%885%E5%A4%84%EF%BC%8C%E5%85%A8%E5%B9%B4%E5%B8%B8%E9%A9%BB%EF%BC%89" tabindex="-1">🎨 街头艺术壁画（5处，全年常驻）</h3><p>这些是画在建筑墙面上的街头艺术，不太好找，得凑近了看：</p><ol><li><strong>山崎若菜壁画</strong>：志摩山·奈良井宿南侧外墙</li><li><strong>岩崎健司壁画</strong>：外山滑雪场南侧建筑东墙</li><li><strong>Dragon76巨龙壁画</strong>：白川乡北侧白楼北墙</li><li><strong>Naoshi蓝底卡通壁画</strong>：大谷无线电塔下方房屋东墙</li><li><strong>爱子女士壁画</strong>：江户川棒球场附近临街墙面</li></ol><h3 id="%F0%9F%8D%83-%E5%AD%A3%E8%8A%82%E9%99%90%E5%AE%9A%E6%94%B6%E9%9B%86%E7%89%A9%EF%BC%888%E4%B8%AA%EF%BC%8C%E9%94%99%E8%BF%87%E8%A6%81%E7%AD%89%EF%BC%89" tabindex="-1">🍃 季节限定收集物（8个，错过要等）</h3><p>这个最坑，不查攻略根本不知道有些东西只在特定季节刷：</p><p><strong>🌸 春季限定：鲤鱼旗（3处）</strong></p><ul><li>大谷梅家门口</li><li>东京铁塔下</li><li>伊东北部沿海小镇</li></ul><p><strong>☀️ 夏季限定：风筝 / 水灯 / 七夕短册</strong></p><ul><li><strong>风筝</strong>：志摩山奈良井宿、地平线体育场东北</li><li><strong>水灯</strong>：平和鸟居湖面、南野东部石桥</li><li><strong>七夕短册</strong>：北部山区小镇、大谷梅家、东京中央车站旁</li></ul><blockquote><p>📌 我的建议：先把13个固定地标+5个壁画清了，季节限定的碰到就拍，碰不到等轮换。不用为了一两个限定专门调季节，随缘就好。</p></blockquote><hr /><h2 id="%F0%9F%93%B7-%E5%85%B3%E4%BA%8E%E6%B8%B8%E6%88%8F%E7%85%A7%E7%89%87%EF%BC%9A%E5%88%AB%E5%8F%AA%E7%82%B9%22%E4%BF%9D%E5%AD%98%22%EF%BC%81" tabindex="-1">📷 关于游戏照片：别只点&quot;保存&quot;！</h2><p>这是我踩过的第一个大坑。</p><p>刚开始玩的时候，拍了一堆照片点了保存，然后去文件夹里找……死活找不到。后来才知道：</p><blockquote><p>⚠️ <strong>只点【保存】= 图片加密锁在游戏本地缓存里，普通文件夹扒不到原图</strong></p><p>必须选【保存+分享】，才能在官网下载到4K无损原图。</p></blockquote><h3 id="%E4%B8%89%E7%A7%8D%E5%AD%98%E5%9B%BE%E6%96%B9%E5%BC%8F%E5%AF%B9%E6%AF%94" tabindex="-1">三种存图方式对比</h3><table><thead><tr><th>方法</th><th>画质</th><th>操作难度</th><th>推荐度</th></tr></thead><tbody><tr><td><strong>官网下载（保存+分享）</strong></td><td>4K无损、无HUD</td><td><a href="http://xn--forza-152j815ke25aonza.net" target="_blank">需要登录forza.net</a></td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><strong>本地截图（Alt+F1/F12）</strong></td><td>看显卡设置</td><td>最简单</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>提取加密缓存</strong></td><td>理论无损</td><td>极其麻烦</td><td>❌ 不推荐</td></tr></tbody></table><h3 id="%E5%8F%AA%E4%BF%9D%E5%AD%98%E6%B2%A1%E5%88%86%E4%BA%AB%EF%BC%9F%E5%88%AB%E6%85%8C%EF%BC%8C%E8%BF%98%E8%83%BD%E6%95%91" tabindex="-1">只保存没分享？别慌，还能救</h3><p>如果你跟我一样，前期拍了一大堆照片只点了&quot;保存&quot;、忘了点&quot;分享&quot;，这些照片<strong>并没有丢</strong>，只是加密存在本地缓存里，<a href="http://forza.net" target="_blank">forza.net</a> 上看不到。</p><p><strong>补救方法</strong>：</p><ol><li>游戏内 <strong>ESC → 创意中心 → 我的创意中心 → 相片图库</strong></li><li>这里能看到所有只保存、没分享的照片</li><li>点开一张 → 选 <strong>共享</strong> → 命名 → 确认上传</li><li>传完去 <a href="http://forza.net" target="_blank">forza.net</a> 就能下载原图了</li></ol><blockquote><p>⚠️ <strong>没有批量一键分享</strong>，只能一张张来。几十张还能忍，上百张就很痛苦了。</p><p><strong>折中方案</strong>（PC）：相片图库打开照片 → 隐藏UI → Steam F12 / Win截图直接截，快但画质有损失，不如官方4K原图清晰。</p></blockquote><p><strong>血泪教训</strong>：以后拍照直接选「保存并分享」，多一步而已，省得后面补救。</p><h3 id="%E6%88%91%E5%B8%B8%E7%94%A8%E7%9A%84%E6%96%B9%E5%BC%8F" tabindex="-1">我常用的方式</h3><ol><li><strong>英伟达显卡</strong>：照相模式隐藏UI后 <code>Alt+F1</code>，自动存到 <code>Videos\Captures\NVIDIA GeForce Experience</code></li><li><strong>Steam版</strong>：照相模式F12截图，在 <code>Steam\userdata\账号\760\remote\screenshots</code></li><li><strong>想要4K无HUD原图</strong>：拍照选保存并分享 → 去 <code>forza.net/myforza</code> 登录同账号 → 相册里直接Download</li></ol><blockquote><p>💡 小技巧：<a href="http://forza.net" target="_blank">forza.net</a> 上的照片是和账号绑定的，换电脑也能找到，相当于云相册了。</p></blockquote><hr /><h2 id="%F0%9F%94%A7-%E6%94%B9%E8%A3%85%E7%AF%87%EF%BC%9A%E4%BB%8E%22%E5%91%B1%E7%9A%87%22%E8%AF%B4%E8%B5%B7" tabindex="-1">🔧 改装篇：从&quot;呱皇&quot;说起</h2><p>这部分是我花时间最多的，也是最想聊的。</p><p>地平线的改装系统深不见底，但核心逻辑其实很简单——<strong>PI点数是加权计分的，不同配件的&quot;性价比&quot;天差地别</strong>。</p><h3 id="%E4%BB%80%E4%B9%88%E6%98%AF%22%E5%91%B1%E7%9A%87%22%E6%94%B9%E8%A3%85%EF%BC%9F-%F0%9F%90%B8" tabindex="-1">什么是&quot;呱皇&quot;改装？ 🐸</h3><p>圈内俗称的&quot;呱皇&quot;（也叫瓜车），说白了就是：</p><blockquote><p><strong>用最少的PI换最大的抓地和加速，卡死S1下限801，不浪费一点PI在低效马力上。</strong></p></blockquote><p>新手改车的通病是先把引擎拉满，结果PI直接爆到850+，车又重又难控。呱皇思路正好反过来——<strong>先把底盘拉满（胎、减重、刹车、悬挂），最后剩多少PI再补动力</strong>。</p><p>同PI下，呱皇改法的弯道速度比纯堆马力快一截，尤其是日本这种多弯山道地图，简直是为呱皇量身定做的。</p><h3 id="%E5%91%B1%E7%9A%87%E5%9B%BA%E5%AE%9A%E6%94%B9%E8%A3%85%E9%A1%BA%E5%BA%8F%EF%BC%88s1-800%E9%80%9A%E7%94%A8%EF%BC%89" tabindex="-1">呱皇固定改装顺序（S1 800通用）</h3><pre><code class="language-">1. 轮胎 → 赛车R胎 + 最大胎宽（优先级TOP1，少量PI换巨额抓地）2. 轻量化 → 全套赛事减重 + 防滚架（减重性价比全游戏最高）3. 刹车 → 全赛事刹车（重刹不推头，山道刚需）4. 悬挂 → 赛事弹簧 + 全套防倾杆（解锁全部微调界面）5. 传动 → 赛事差速 &gt; 赛事离合 &gt; 赛事变速箱6. 动力（最后补）→ 只上ECU+进气，PI还差一点再小涡轮</code></pre><blockquote><p>🎯 改完看右上角PI，<strong>801立刻停手</strong>，多余PI一点不加。这就是标准呱皇S1车。</p></blockquote><h3 id="%E6%97%A5%E7%B3%BB%E5%9B%9B%E5%8F%B0%E8%B5%9B%E5%AD%A3%E8%BD%A6%E5%91%B1%E7%9A%87%E6%96%B9%E6%A1%88" tabindex="-1">日系四台赛季车呱皇方案</h3><p>这些是我自己照着改过的，直接抄作业就行：</p><table><thead><tr><th>车型</th><th>原厂PI</th><th>改法要点</th><th>最终PI</th></tr></thead><tbody><tr><td><strong>R34 GTR (1999)</strong></td><td>742</td><td>底盘全满 + ECU+进气 + 小号涡轮</td><td>802</td></tr><tr><td><strong>EVO9 MR</strong></td><td>721</td><td>底盘全满 + ECU + 小涡轮</td><td>805</td></tr><tr><td><strong>FD3S RX7</strong></td><td>710</td><td>底盘拉满 + 仅ECU微调</td><td>803</td></tr><tr><td><strong>Supra A80</strong></td><td>736</td><td>底盘满 + 进气 + 空滤</td><td>801</td></tr></tbody></table><h3 id="%E5%87%A0%E4%B8%AA%E5%91%B1%E7%9A%87%E9%93%81%E5%BE%8B-%E2%9A%A1" tabindex="-1">几个呱皇铁律 ⚡</h3><ol><li><strong>四驱车优先保留原厂四驱</strong>，不要改后驱/前驱——改驱动消耗巨量PI，呱皇大忌</li><li><strong>能不换发动机就不换</strong>，换引擎动辄几十PI，极易爆PI</li><li><strong>空气动力学按需加</strong>：城区短道不加尾翼省PI；长山道加前后包围</li><li><strong>PI容错技巧</strong>：PI到798（A顶），只加一个小进气刚好801进S1</li></ol><h3 id="%E5%BA%95%E7%9B%98%E7%B2%BE%E7%BB%86%E8%B0%83%E6%A0%A1%EF%BC%88%E5%91%B1%E7%9A%87%E6%94%B6%E5%B0%BE%EF%BC%89" tabindex="-1">底盘精细调校（呱皇收尾）</h3><p>改完配件一定要调，不然白瞎了好配件：</p><ul><li><strong>胎压</strong>：公路30PSI前后；山道前29、后31（解决推头）</li><li><strong>最终传动比</strong>：缩短齿轮（偏加速），日本地图全是连续弯道，出弯提速 &gt; 极速</li><li><strong>防倾杆</strong>：前软后硬（改善入弯推头，日系通病）；RX7后驱可以更硬后防倾防甩尾</li><li><strong>下压力</strong>：山道前50后70，城市短途前35后45</li></ul><hr /><h2 id="%F0%9F%93%8A-%E5%85%A8%E8%BD%A6%E5%9E%8B%E9%80%9A%E7%94%A85%E5%A4%A7%E6%94%B9%E8%A3%85%E8%B7%AF%E7%BA%BF" tabindex="-1">📊 全车型通用5大改装路线</h2><p>其实所有车的改装就五个方向，想清楚你要干嘛再改，别瞎堆配件：</p><h3 id="1%EF%B8%8F%E2%83%A3-circuit-%E8%B5%9B%E9%81%93%E6%8A%93%E5%9C%B0%EF%BC%88%E5%B1%B1%E8%B7%AF%2F%E7%8E%AF%E8%B5%9B%E8%81%94%E6%9C%BA%EF%BC%89" tabindex="-1">1️⃣ Circuit 赛道抓地（山路/环赛联机）</h3><p><strong>核心：高抓地、高下压力、均衡加速+过弯，放弃极限极速</strong></p><ul><li>轮胎：热熔竞速胎，优先级 &gt; 引擎</li><li>动力：中高马力、线性出力，机增/小涡轮</li><li>传动：密齿比变速箱，最终传动偏短</li><li>空力：大前铲 + 大尾翼，最大化下压力</li><li>悬挂：低趴车身、偏硬弹簧</li><li>驱动：多弯公路优先四驱</li></ul><blockquote><p>极速普遍320～360km/h，联机比赛标配</p></blockquote><h3 id="2%EF%B8%8F%E2%83%A3-topspeed-%E6%9E%81%E9%99%90%E6%9E%81%E9%80%9F%EF%BC%88%E6%B5%8B%E9%80%9F%2F%E5%8D%B1%E9%99%A9%E6%A0%87%E5%BF%97%EF%BC%89" tabindex="-1">2️⃣ TopSpeed 极限极速（测速/危险标志）</h3><p><strong>核心：最小风阻、长齿比，牺牲起步和拐弯，只堆尾速</strong></p><ul><li>轮胎：高压光头胎，减小滚动阻力</li><li>动力：大排量V10/V12 + 双涡轮，极致减重</li><li>传动：超长齿比，拉高终传</li><li>空力：最小前唇 + 微型小鸭尾，几乎零下压力</li><li>悬挂：车身抬高、悬挂偏软</li><li>驱动：一律改四驱，防止高速飘车</li></ul><blockquote><p>任何车都能改成极速车，原厂越野车改完也能400+</p></blockquote><h3 id="3%EF%B8%8F%E2%83%A3-drag-%E7%9B%B4%E7%BA%BF%E5%8A%A0%E9%80%9F%EF%BC%880-400%E7%B1%B3%E5%BC%B9%E5%B0%84%E8%B5%B7%E6%AD%A5%EF%BC%89" tabindex="-1">3️⃣ Drag 直线加速（0-400米弹射起步）</h3><p><strong>核心：起步抓地、低转大扭矩，前3档爆加速</strong></p><ul><li>轮胎：加宽热熔直线胎，后轮拉满胎宽</li><li>动力：极限涡轮，全段大扭矩</li><li>传动：超密低速齿比</li><li>悬挂：前软后硬，起步车头抬起压后轮</li><li>驱动：必改四驱（后驱大马力必挠胎）</li></ul><h3 id="4%EF%B8%8F%E2%83%A3-rally%2Foffroad-%E6%8B%89%E5%8A%9B%E8%B6%8A%E9%87%8E%EF%BC%88%E6%B3%A5%E5%9C%B0%E3%80%81%E7%A0%82%E7%9F%B3%E3%80%81%E4%B8%9B%E6%9E%97%EF%BC%89" tabindex="-1">4️⃣ Rally/Offroad 拉力越野（泥地、砂石、丛林）</h3><p><strong>和公路改装完全反向：离地高、低扭强、烂路抓地</strong></p><ul><li>轮胎：全地形越野胎/拉力碎石胎（绝对不能用公路胎）</li><li>动力：低扭取向，不用高转大马力</li><li>悬挂：升高越野悬挂，最大离地间隙</li><li>驱动：全系四驱</li><li>传动：越野专用变速箱，放大低速扭矩</li></ul><h3 id="5%EF%B8%8F%E2%83%A3-drift-%E6%BC%82%E7%A7%BB%EF%BC%88%E5%8F%AF%E6%8E%A7%E6%8C%81%E7%BB%AD%E6%89%93%E6%BB%91%EF%BC%89" tabindex="-1">5️⃣ Drift 漂移（可控持续打滑）</h3><p><strong>核心：大马力 + 后轮易打滑，前轮抓地控方向</strong></p><ul><li>驱动：四驱改后驱（漂移刚需）</li><li>轮胎：漂移专用胎，前低后高压</li><li>动力：大马力涡轮</li><li>悬挂：整体偏硬，差速锁止拉满</li><li>刹车：刹车配比靠前，轻点刹车甩尾入弯</li></ul><blockquote><p>📌 记住这个顺序：<strong>轮胎 &gt; 悬挂 &gt; 传动 &gt; 刹车 &gt; 动力 &gt; 空力</strong>。新手别一上来就换引擎堆马力，那是最慢的改法。</p></blockquote><hr /><h2 id="%F0%9F%9A%80-%E6%B5%8B%E9%80%9F%E4%B8%93%E7%94%A8%EF%BC%9Amx5-fe-%E6%9E%81%E9%80%9F%E6%94%B9%E8%A3%85" tabindex="-1">🚀 测速专用：MX5 FE 极速改装</h2><p>说到测速，必须单独拎出来说 MX5 FE（1994马自达FE）。这台车是测速界的怪物，改完平路440km+，蹭尾流能到470km，全测速点三星随便拿。</p><h3 id="%E9%85%8D%E4%BB%B6%E6%94%B9%E8%A3%85%E8%A6%81%E7%82%B9" tabindex="-1">配件改装要点</h3><p><strong>引擎：必换蝰蛇V10双涡轮</strong></p><ul><li>发动机置换：Dodge蝰蛇V10双涡轮（原厂引擎没用）</li><li>进气：全赛车高流量进气 + 赛车凸轮轴</li><li>排气：全段赛车直通排气</li><li>冷却：全套机油/水冷强化、赛车ECU、双涡轮套件全顶配</li></ul><p><strong>传动</strong></p><ul><li>6速全竞技序列变速箱（必须6档，拉长高速齿比）</li><li>全赛车竞技离合</li><li>全竞技限滑差速</li></ul><p><strong>底盘&amp;减重</strong></p><ul><li>全套碳纤维轻量化、拆多余内饰、最轻防滚架</li><li>拉力竞技悬挂（高速稳、颠簸不飘，比赛道悬挂更适合长直）</li></ul><p><strong>轮胎轮毂</strong></p><ul><li>直线加速热熔胎；前轮窄胎、后轮胎宽拉满</li><li>最轻竞技轻量化轮毂</li></ul><p><strong>空气动力学【极速关键！】</strong></p><ul><li>前扰流：原厂最小前唇</li><li>后尾翼：最小鸭尾</li><li><strong>禁用高 downforce 赛车尾翼！</strong></li></ul><blockquote><p>⚠️ 空力配件只选外观最小件，下压力能多低多低。大尾翼=大风阻=跑不快。</p></blockquote><h3 id="%E7%B2%BE%E7%BB%86%E8%B0%83%E6%A0%A1%E6%95%B0%E5%80%BC" tabindex="-1">精细调校数值</h3><table><thead><tr><th>项目</th><th>数值</th><th>说明</th></tr></thead><tbody><tr><td>最终传动比</td><td>拉到最左（极速方向）</td><td>5、6档全部拉最长</td></tr><tr><td>前下压力</td><td>5%</td><td>全游戏最低区间</td></tr><tr><td>后下压力</td><td>6%</td><td>几乎无阻力</td></tr><tr><td>前轮胎压</td><td>52PSI</td><td>高压减接地、降风阻</td></tr><tr><td>后轮胎压</td><td>16PSI</td><td>低压加大接地、防打滑</td></tr><tr><td>前轮外倾角</td><td>-0.8</td><td></td></tr><tr><td>后轮外倾角</td><td>-0.4</td><td></td></tr><tr><td>前束</td><td>+0.12</td><td>轻微外八提升直线稳定</td></tr><tr><td>后束</td><td>-0.1</td><td></td></tr><tr><td>车身高度</td><td>拉到最高</td><td>高速防贴地乱飘</td></tr><tr><td>差速加速锁死</td><td>78%</td><td>后驱大马力防起步空转</td></tr><tr><td>差减速锁死</td><td>12%</td><td></td></tr></tbody></table><h3 id="%E6%B5%8B%E9%80%9F%E5%AE%9E%E6%93%8D%E6%8A%80%E5%B7%A7" tabindex="-1">测速实操技巧</h3><ol><li><strong>下坡起步</strong>：测速点从远距离下坡满油起步，下坡天然+15km时速</li><li><strong>蹭尾流</strong>：跟在AI后面蹭尾流，额外+25～30km，轻松460+</li><li><strong>这套只用来测速</strong>，跑环岛/赛事再切另一套弯道改装</li></ol><h3 id="%E6%87%92%E4%BA%BA%E6%8D%B7%E5%BE%84%EF%BC%9A%E7%A4%BE%E5%8C%BA%E8%B0%83%E6%A0%A1%E5%88%86%E4%BA%AB%E7%A0%81" tabindex="-1">懒人捷径：社区调校分享码</h3><p>不想手动改？直接输分享码一键下载全套改装+调校：</p><table><thead><tr><th>分享码</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>173611186</strong></td><td>全网热门极速款，平地442km/h</td><td>所有测速抓拍通关首选</td></tr><tr><td><strong>769106398</strong></td><td>长直道尾流特化，蹭尾流470km</td><td>大桥长测速专用</td></tr><tr><td><strong>159766283</strong></td><td>兼顾加速+极速，起步不打滑</td><td>下坡测速轻松超标</td></tr></tbody></table><p><strong>输入步骤</strong>：暂停 → 车辆 → 调校车辆 → 查找调校 → 输入共享代码 → 粘贴数字 → 下载</p><blockquote><p>💡 装好后记得点【保存调校】命名「MX5极速测速」，以后一键切换。</p></blockquote><hr /><h2 id="%F0%9F%92%A8-%E6%BC%82%E7%A7%BB%E4%B8%89%E6%98%9F%EF%BC%9A%E9%95%BF%E8%BF%9E%E6%BC%82%E6%89%8D%E6%98%AF%E7%8E%8B%E9%81%93" tabindex="-1">💨 漂移三星：长连漂才是王道</h2><p>漂移计分的核心机制搞懂了，三星其实不难：</p><blockquote><p><strong>持续漂移 = 连击倍率不断上涨（×1→×2→×3→×5逐级翻倍），断漂直接倍率清零重算</strong></p><p>分数 = 单米基础分 × 车速系数 × 车身角度 × 连击倍数</p></blockquote><h3 id="%E4%B8%A4%E7%A7%8D%E6%89%93%E6%B3%95%E5%AF%B9%E6%AF%94" tabindex="-1">两种打法对比</h3><table><thead><tr><th>打法</th><th>上限</th><th>难度</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>长距离连贯漂移</strong></td><td>极高</td><td>中等</td><td>绝大多数常规漂移区</td></tr><tr><td><strong>短距离频繁分段漂</strong></td><td>低</td><td>简单</td><td>狭窄发卡弯、新手救场</td></tr></tbody></table><p><strong>结论：想稳拿三星，优先全程连贯长距离连漂。</strong> 短漂只适合新手补分，冲高分没戏。</p><h3 id="%E9%95%BF%E8%BF%9E%E6%BC%82%E5%AE%9E%E6%93%8D%E8%A6%81%E7%82%B9" tabindex="-1">长连漂实操要点</h3><ol><li><strong>提前起漂</strong>：入区前就在外道起漂，不要等进蓝线才开始滑，浪费起步路程</li><li><strong>贴边线滑行</strong>：左右贴满车道边线，把路面宽度全部转化成漂移里程</li><li><strong>车速控制</strong>：70–110km/h最佳，太慢基础分暴跌，太快容易失控</li><li><strong>角度控制</strong>：车身30°～45°横滑，角度太小得分低，太大容易撞墙</li><li><strong>撞墙 = 倍率清零</strong>：宁可小角度慢漂，不要大角度上墙断分</li></ol><h3 id="%E6%96%B0%E6%89%8B%E4%B8%87%E8%83%BD%E6%8A%98%E4%B8%AD%E6%96%B9%E6%A1%88" tabindex="-1">新手万能折中方案</h3><p>长直道全程一漂到底，连续急弯每个弯道衔接不回正、连贯切换左右漂移，只在实在控不住时短暂收油微调——做到&quot;分段但不断连击&quot;，兼顾容错和倍率。</p><h3 id="%E8%BD%A6%E8%BE%86%E8%AE%BE%E7%BD%AE%E5%BB%BA%E8%AE%AE" tabindex="-1">车辆设置建议</h3><ul><li>手动挡锁2/3档不动</li><li>全关TC/ESP</li><li>优先四驱漂移调校（容错高、更容易稳住长漂不打转）</li></ul><hr /><h2 id="%F0%9F%8F%AF-jdm-%E6%83%85%E6%80%80%E8%BD%A6%EF%BC%9A%E6%97%A5%E6%9C%AC%E5%9C%B0%E5%9B%BE%E7%9A%84%E6%AD%A3%E7%A1%AE%E6%89%93%E5%BC%80%E6%96%B9%E5%BC%8F" tabindex="-1">🏯 JDM 情怀车：日本地图的正确打开方式</h2><p>既然是日本地图，不开JDM说不过去。下面这些是车迷公认的经典，按风格分了四类：</p><h3 id="%F0%9F%94%A5-%E6%88%98%E7%A5%9E%E4%B8%8E%E8%B6%85%E8%B7%91%E7%BA%A7%EF%BC%88jdm%E5%A4%A9%E8%8A%B1%E6%9D%BF%EF%BC%89" tabindex="-1">🔥 战神与超跑级（JDM天花板）</h3><p><strong>日产 Skyline GT-R（R32 / R33 / R34）</strong></p><ul><li>绰号：哥斯拉、东瀛战神</li><li>动力：RB26DETT 2.6L双涡轮直六，标称276匹，轻松400-600匹</li><li>核心：ATTESA E-TS四驱 + Super-HICAS四轮转向</li><li>代表：R32（89-94）、R34（99-02，颜值巅峰）</li></ul><p><strong>丰田 Supra A80（MK4，1993-2002）</strong></p><ul><li>外号：牛魔王、2JZ神车</li><li>动力：2JZ-GTE 3.0L双涡轮直六，铸铁缸体，原厂276匹可干到800-1000匹</li><li>特点：改装界第一神机，直线加速/漂移常客</li></ul><p><strong>本田 NSX（初代，1990-2005）</strong></p><ul><li>定位：日本首款中置超跑，“东瀛法拉利”</li><li>动力：3.0L/3.2L高转自吸V6，铝制车身轻量化</li><li>意义：证明日系能做超跑，F1技术下放，塞纳参与调校</li></ul><h3 id="%F0%9F%9A%97-%E6%BC%82%E7%A7%BB%2F%E5%90%8E%E9%A9%B1%E7%BB%8F%E5%85%B8%EF%BC%88%E5%A4%B4%E6%96%87%E5%AD%97d%E4%B8%BB%E5%8A%9B%EF%BC%89" tabindex="-1">🚗 漂移/后驱经典（头文字D主力）</h3><p><strong>丰田 AE86（Corolla Levin / Trueno，1983-1987）</strong></p><ul><li>地位：漂移始祖、豆腐车、JDM文化符号</li><li>动力：4A-GE 1.6L高转自吸四缸，130匹，后驱+超轻车身（920kg）</li><li>标签：便宜、耐造、改装潜力大、新手漂移首选</li></ul><p><strong>马自达 RX-7（FC3S / FD3S，1985-2002）</strong></p><ul><li>引擎：13B双转子发动机，8000转+红线，声浪独特</li><li>FC3S：高桥凉介座驾，“转子女神”</li><li>FD3S：更宽体更强动力，最终转子绝唱</li></ul><p><strong>日产 Silvia（S13 / S14 / S15，1988-2002）</strong></p><ul><li>定位：漂移之王、平民后驱神车</li><li>动力：SR20DET 2.0L涡轮四缸，200-250匹</li><li>代表：S15（最终版，颜值+操控巅峰）</li></ul><h3 id="%F0%9F%8F%94%EF%B8%8F-%E6%8B%89%E5%8A%9B%2F%E5%9B%9B%E9%A9%B1%E7%8C%9B%E5%85%BD%EF%BC%88wrc%E5%86%A0%E5%86%9B%E5%9F%BA%E5%9B%A0%EF%BC%89" tabindex="-1">🏔️ 拉力/四驱猛兽（WRC冠军基因）</h3><p><strong>三菱 Lancer Evolution（EVO Ⅰ-Ⅹ，1992-2016）</strong></p><ul><li>简称：EVO，枪骑兵</li><li>动力：4G63 2.0L红头涡轮四缸，铸铁缸体，原厂280匹可500+匹</li><li>核心：超级四驱（S-AWC）+ 完美底盘，WRC冠军</li><li>经典：EVO Ⅳ、Ⅵ、Ⅸ</li></ul><p><strong>斯巴鲁 Impreza WRX STI（GC8 / GD，1992-2007）</strong></p><ul><li>简称：STI，扫把佬</li><li>动力：EJ20 2.0L水平对置涡轮四缸，对称四驱</li><li>特点：拉力赛王者、冰雪路面最强、高转暴力</li><li>经典：GC8（90年代）、GD（2000年代）</li></ul><h3 id="%E2%9A%A1-%E6%9C%AC%E7%94%B0%E9%AB%98%E8%BD%AC%E4%B8%8E%E5%B0%8F%E9%92%A2%E7%82%AE%EF%BC%88na%E7%B2%BE%E7%A5%9E%E4%BB%A3%E8%A1%A8%EF%BC%89" tabindex="-1">⚡ 本田高转与小钢炮（NA精神代表）</h3><p><strong>本田 S2000（AP1 / AP2，1999-2009）</strong></p><ul><li>定位：后驱敞篷、高转之王、NA巅峰</li><li>动力：F20C 2.0L自吸四缸，9000转红线，240匹</li><li>特点：50:50配重、极致操控、声浪炸裂</li></ul><p><strong>本田 Integra Type R（DC2，1995-2001）</strong></p><ul><li>简称：DC2，型格Type R</li><li>动力：B18C 1.8L高转自吸四缸，8400转红线，195匹</li><li>特点：轻量化、前驱操控天花板、VTEC爆TEC</li></ul><p><strong>本田 Civic Type R（EK9，1997-2000）</strong></p><ul><li>简称：EK9，思域Type R初代</li><li>动力：B16B 1.6L高转自吸四缸，8200转红线，185匹</li><li>意义：本田性能车入门标杆、轻量化前驱小钢炮</li></ul><hr /><h2 id="%F0%9F%92%BE-%E6%94%B9%E8%A3%85%E5%AD%98%E6%A1%A3%EF%BC%9A%E4%B8%80%E8%BD%A6%E5%A4%9A%E6%96%B9%E6%A1%88%EF%BC%8C%E4%B8%8D%E7%94%A8%E9%87%8D%E5%A4%8D%E4%B9%B0%E8%BD%A6" tabindex="-1">💾 改装存档：一车多方案，不用重复买车</h2><p>这事儿我也纠结了很久，后来搞明白了：</p><h3 id="%E9%85%8D%E4%BB%B6%E6%98%AF%E6%B0%B8%E4%B9%85%E7%9A%84-%F0%9F%94%92" tabindex="-1">配件是永久的 🔒</h3><p>你给一台车花钱买了赛车胎、减重、涡轮，<strong>零件永久属于这辆车</strong>。后续切任何调校、换回原厂，零件不会被回收、不用重复花钱买。</p><p>切换调校只是启用/停用配件，配件所有权永久保留在该车档案。云端存档同步，换电脑也不丢。</p><h3 id="%E8%B0%83%E6%A0%A1%E6%96%B9%E6%A1%88%E5%8F%AF%E4%BB%A5%E5%AD%98%E5%A4%9A%E5%A5%97-%F0%9F%93%81" tabindex="-1">调校方案可以存多套 📁</h3><p>车库 → 升级 → <strong>保存调校（我的调校）</strong>，一台车能存N套改装方案，随时一键套用。</p><p>比如R34可以存：</p><ul><li>① S1呱皇山道802PI</li><li>② A级原厂代步</li><li>③ 漂移后驱</li></ul><blockquote><p>缺点：当前车辆只能生效1套，不能同时挂载多套。</p></blockquote><h3 id="%E9%87%8D%E5%A4%8D%E5%9B%A4%E8%BD%A6-%3D-%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%B8%E7%8E%A9%E6%B3%95-%F0%9F%9A%97%F0%9F%9A%97%F0%9F%9A%97" tabindex="-1">重复囤车 = 官方默许玩法 🚗🚗🚗</h3><p>完全允许重复购买同款车，就是为了分开存放不同改装。</p><p>两种玩法对比：</p><table><thead><tr><th>方案</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>1台车存多套调校</strong></td><td>省钱、省车库位</td><td>进比赛前必须切调校，联机容易忘改PI</td></tr><tr><td><strong>多囤同款车</strong></td><td>每台车改装固定，点开直接参赛</td><td>占车库位、多花钱</td></tr></tbody></table><blockquote><p>🎯 刷赛季任务推荐多囤同款，不用赛前反复改车，省心。日系热门JDM囤2～3台足够覆盖全场景。</p></blockquote><h3 id="%E7%A4%BE%E5%8C%BA%E8%B0%83%E6%A0%A1%EF%BC%9A%E4%B8%80%E9%94%AE%E6%8A%84%E4%BD%9C%E4%B8%9A-%F0%9F%93%8B" tabindex="-1">社区调校：一键抄作业 📋</h3><p>不想自己改？直接下载大佬的现成调校：</p><p><strong>入口</strong>：车库选中车 → 升级与调校 → 查找调校</p><p><strong>搜索关键词含义</strong>：</p><ul><li>TOP SPEED / 极速 = 纯直线极速调校（测速专用）</li><li>Circuit = 赛道弯多调校</li><li>Drag = 直线加速</li><li>Drift = 漂移</li></ul><p><strong>筛选条件</strong>：等级对、标签对、按下载量/点赞排序，优先几十万下载的热门调校。</p><blockquote><p>💡 下载调校第一次要花钱买零件，<strong>已经买过的配件切换存档不重复扣费</strong>。</p></blockquote><hr /><h2 id="%F0%9F%8E%AB-%E8%B1%AA%E5%8D%8E%E7%89%88%E8%BD%A6%E7%A5%A8%E6%80%8E%E4%B9%88%E7%94%A8%E6%9C%80%E5%88%92%E7%AE%97%EF%BC%9F" tabindex="-1">🎫 豪华版车票怎么用最划算？</h2><p>最后说个新手常见问题：豪华版送的那张车展兑换券，换啥最值？</p><h3 id="%E5%85%88%E6%90%9E%E6%B8%85%E6%A5%9A%E6%95%B0%E9%87%8F" tabindex="-1">先搞清楚数量</h3><ul><li><strong>豪华版/尊享版：只送1张车展兑换券（Car Voucher）</strong></li><li>放在欢迎包里：5辆预调车 + 1张车票 + 3张服装券</li><li>作用：车展里随便换一辆车，不要钱，但换完不能拍卖，只能自己留着开</li></ul><h3 id="%E4%B8%A4%E7%A7%8D%E6%80%9D%E8%B7%AF" tabindex="-1">两种思路</h3><p><strong>思路A：换最贵的车（纯血收藏/省钱）</strong></p><ul><li><strong>Ferrari 330 P4：70,000,000 CR</strong></li><li>用券直接省7000万，收藏价值拉满</li></ul><p><strong>思路B：换歌莉娅/巨汉T0神车（实用党首选）</strong></p><ul><li><strong>马自达787B</strong>：歌莉娅当前最强S2，抓地无敌，AI杀手</li><li><strong>Koenigsegg Jesko</strong>：巨汉最强，直线+高速弯统治力</li><li><strong>Rimac Nevera</strong>：四驱最稳，容错最高，新手歌莉娅首选</li></ul><blockquote><p>🎯 我的建议：如果你已经有FXX-K Evo（欢迎包送），Jesko可以慢慢攒钱买，<strong>最推荐用券换787B</strong>，直接解决歌莉娅被AI拉开的问题。</p><p>别浪费在便宜车上——FXX-K Evo才25万，不值一张券。</p></blockquote><hr /><h2 id="%F0%9F%93%9D-%E6%9C%80%E5%90%8E%E8%AF%B4%E4%B8%A4%E5%8F%A5" tabindex="-1">📝 最后说两句</h2><p>地平线6这代，我个人觉得是系列里&quot;内容密度&quot;最高的一作。日本地图的多样性、JDM文化的深度、改装系统的复杂度，都够你玩很久。</p><p>但也别被海量内容吓到。我的建议是：</p><ol><li><strong>先逛地图</strong>，把26个地标清了，熟悉地形</li><li><strong>选一台你喜欢的车</strong>，先改一套呱皇S1，跑通赛事循环</li><li><strong>再慢慢探索</strong>测速、漂移、拉力这些支线玩法</li><li><strong>攒钱买车库</strong>，一车一方案，不用急着全收集</li></ol><p>地平线的乐趣从来不是&quot;全清&quot;，而是开着你喜欢的车，在喜欢的路上瞎逛。</p><p>祝玩得开心 🏁</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Fail2Ban 简易使用指南]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/fail2ban" />
                <id>tag:https://fx67ll.xyz,2026-08-06:fail2ban</id>
                <published>2026-08-06T19:48:26+08:00</published>
                <updated>2026-08-08T00:26:18+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="fail2ban-%E9%98%B2%E6%8A%A4%E9%83%A8%E7%BD%B2%E4%B8%8E%E8%BF%90%E7%BB%B4%E6%89%8B%E5%86%8C" tabindex="-1">Fail2Ban 防护部署与运维手册</h1><blockquote><p>适配宝塔 Linux 面板，生产可用，覆盖 <strong>SSH + Nginx SSL + MySQL + Redis + MongoDB</strong> 五套端口防护，内置 Recidive 惯犯加重封禁机制。</p></blockquote><hr /><h2 id="%E4%B8%80%E3%80%81%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B" tabindex="-1">一、快速上手</h2><h3 id="1.1-%E5%AE%89%E8%A3%85%E4%B8%8E%E5%90%AF%E5%8A%A8" tabindex="-1">1.1 安装与启动</h3><pre><code class="language-bash"># 安装apt update -y &amp;&amp; apt install fail2ban -y# 开机自启 + 启动systemctl enable --now fail2bansystemctl restart fail2ban</code></pre><h3 id="1.2-%E6%A0%B8%E5%BF%83%E5%8F%82%E6%95%B0%E9%80%9F%E8%A7%88" tabindex="-1">1.2 核心参数速览</h3><table><thead><tr><th>参数</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>maxretry</code></td><td>5</td><td>最大失败次数</td></tr><tr><td><code>findtime</code></td><td>600</td><td>统计时间窗口（秒），即 10 分钟内</td></tr><tr><td><code>bantime</code></td><td>86400</td><td>封禁时长（秒），即 24 小时</td></tr><tr><td><code>ignoreip</code></td><td>127.0.0.1</td><td>白名单 IP，<strong>务必加上本机公网 IP</strong></td></tr></tbody></table><blockquote><p>💡 <strong>修改配置后必须执行 <code>fail2ban-client reload</code> 才生效</strong>，不要只重启服务。</p></blockquote><hr /><h2 id="%E4%BA%8C%E3%80%81%E4%BA%94%E5%A5%97%E9%98%B2%E6%8A%A4%E7%9B%91%E7%8B%B1" tabindex="-1">二、五套防护监狱</h2><h3 id="2.1-ssh-%E6%9A%B4%E5%8A%9B%E7%A0%B4%E8%A7%A3%E9%98%B2%E6%8A%A4-%E2%9C%85-%E5%BF%85%E8%A3%85" tabindex="-1">2.1 SSH 暴力破解防护 ✅ 必装</h3><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/sshd.conf &lt;&lt;&#39;EOF&#39;[sshd]enabled = trueport = 22filter = sshdlogpath = /var/log/auth.logmaxretry = 5findtime = 600bantime = 86400ignoreip = 127.0.0.1EOF</code></pre><ul><li><strong>防护对象</strong>：SSH 22 端口密码爆破</li><li><strong>过滤器</strong>：系统内置 <code>sshd</code>，无需自定义</li></ul><hr /><h3 id="2.2-nginx-ssl-%E6%81%B6%E6%84%8F%E6%89%AB%E6%8F%8F%E9%98%B2%E6%8A%A4" tabindex="-1">2.2 Nginx SSL 恶意扫描防护</h3><h4 id="%E8%BF%87%E6%BB%A4%E5%99%A8%E9%85%8D%E7%BD%AE" tabindex="-1">过滤器配置</h4><pre><code class="language-bash">cat &gt; /etc/fail2ban/filter.d/ssl-fail.conf &lt;&lt;&#39;EOF&#39;[Definition]# 匹配 SSL + failed + client:IP 的异常握手报错failregex = .*SSL.*failed.*client: &lt;HOST&gt;ignoreregex =EOF</code></pre><h4 id="%E7%9B%91%E7%8B%B1%E9%85%8D%E7%BD%AE" tabindex="-1">监狱配置</h4><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/ssl-protect.conf &lt;&lt;&#39;EOF&#39;[ssl-protect]enabled = truefilter = ssl-faillogpath = /www/wwwlogs/nginx_error.logmaxretry = 5findtime = 600bantime = 86400banaction = iptables-multiportbanaction_allports = trueignoreip = 127.0.0.1EOF</code></pre><ul><li><strong>防护对象</strong>：Nginx 异常 SSL 握手扫描</li><li><strong>关键特性</strong>：<code>banaction_allports = true</code> → 封禁该 IP 所有端口，避免换端口扫描</li></ul><hr /><h3 id="2.3-mysql-%E6%95%B0%E6%8D%AE%E5%BA%93%E9%98%B2%E7%88%86%E7%A0%B4" tabindex="-1">2.3 MySQL 数据库防爆破</h3><h4 id="%E5%89%8D%E7%BD%AE%E4%BC%98%E5%8C%96%EF%BC%88%E5%BF%85%E9%A1%BB%E6%89%A7%E8%A1%8C%EF%BC%8C%E5%90%A6%E5%88%99%E5%8C%B9%E9%85%8D%E5%A4%B1%E6%95%88%EF%BC%89" tabindex="-1">前置优化（必须执行，否则匹配失效）</h4><pre><code class="language-bash"># 检查是否已开启详细错误日志grep log_error_verbosity /etc/my.cnf# 无输出则写入配置sed -i &#39;/\[mysqld\]/a log_error_verbosity = 3&#39; /etc/my.cnf# 重启 MySQL/etc/init.d/mysqld restart# 赋予日志可读权限chmod 644 /www/server/data/hcss-ecs-695e.err# 查看历史爆破记录grep &quot;Access denied&quot; /www/server/data/hcss-ecs-695e.err | tail -20</code></pre><h4 id="%E8%BF%87%E6%BB%A4%E5%99%A8%E9%85%8D%E7%BD%AE-1" tabindex="-1">过滤器配置</h4><pre><code class="language-bash">cat &gt; /etc/fail2ban/filter.d/mysql.conf &lt;&lt;&#39;EOF&#39;[INCLUDES]before = common.conf[Definition]_daemon = mysqld# 万能匹配：时间开头，中间任意字符，最后匹配 Access denied + &lt;HOST&gt;failregex = ^.*Access denied for user .*@&#39;&lt;HOST&gt;&#39;ignoreregex =EOF</code></pre><h4 id="%E7%9B%91%E7%8B%B1%E9%85%8D%E7%BD%AE-1" tabindex="-1">监狱配置</h4><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/mysql.conf &lt;&lt;&#39;EOF&#39;[mysql]enabled = trueport = 3306filter = mysqllogpath = /www/server/data/hcss-ecs-695e.errmaxretry = 5findtime = 600bantime = 86400action = iptables[name=MySQL, port=3306, protocol=tcp]ignoreip = 127.0.0.1EOF</code></pre><ul><li><strong>防护对象</strong>：MySQL 3306 端口账户暴力破解</li><li><strong>⚠️ 注意</strong>：<code>logpath</code> 中的主机名需替换为你服务器的实际主机名</li></ul><hr /><h3 id="2.4-redis-%E9%98%B2%E7%88%86%E7%A0%B4" tabindex="-1">2.4 Redis 防爆破</h3><h4 id="%E8%BF%87%E6%BB%A4%E5%99%A8%E9%85%8D%E7%BD%AE-2" tabindex="-1">过滤器配置</h4><pre><code class="language-bash">cat &gt; /etc/fail2ban/filter.d/redis.conf &lt;&lt;&#39;EOF&#39;[Definition]failregex = ^.*Failed password for user .* from &lt;HOST&gt;            ^.*Authentication failed for user .* from &lt;HOST&gt;            ^.*ERR invalid password from &lt;HOST&gt;            ^.*Client sent AUTH, but no password is set from &lt;HOST&gt;ignoreregex =EOF</code></pre><h4 id="%E7%9B%91%E7%8B%B1%E9%85%8D%E7%BD%AE-2" tabindex="-1">监狱配置</h4><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/redis.conf &lt;&lt;&#39;EOF&#39;[redis]enabled = trueport = 6379filter = redislogpath = /www/server/redis/redis.logmaxretry = 5findtime = 600bantime = 86400action = iptables[name=Redis, port=6379, protocol=tcp]ignoreip = 127.0.0.1EOF</code></pre><ul><li><strong>防护对象</strong>：Redis 6379 未授权访问 / 密码爆破</li><li><strong>⚠️ 注意</strong>：宝塔 Redis 默认关闭日志，需在面板开启</li></ul><hr /><h3 id="2.5-mongodb-%E9%98%B2%E7%88%86%E7%A0%B4" tabindex="-1">2.5 MongoDB 防爆破</h3><h4 id="%E8%BF%87%E6%BB%A4%E5%99%A8%E9%85%8D%E7%BD%AE-3" tabindex="-1">过滤器配置</h4><pre><code class="language-bash">cat &gt; /etc/fail2ban/filter.d/mongodb.conf &lt;&lt;&#39;EOF&#39;[Definition]# 多种认证失败关键词，兼容各 Mongo 版本failregex = .*&quot;msg&quot;:&quot;(Auth failed|Authentication failed|Authentication failure|login failed|Unauthorized|auth error)&quot;.*&quot;(remote|client)&quot;:&quot;&lt;HOST&gt;:\d+&quot;# 端口扫描、恶意连接重置            .*&quot;errmsg&quot;:&quot;.*Connection reset by peer&quot;.*&quot;(remote|client)&quot;:&quot;&lt;HOST&gt;:\d+&quot;# 异常中途断连探测            .*&quot;Interrupted operation as its client disconnected&quot;.*&quot;(remote|client)&quot;:&quot;&lt;HOST&gt;:\d+&quot;# 适配宝塔 Mongo 标准 JSON 日志时间戳datepattern = {&quot;t&quot;:{&quot;\$date&quot;:&quot;%%Y-%%m-%%dT%%H:%%M:%%S\.%%f\+\d{2}:\d{2}&quot;}}# 正确忽略本地 127.0.0.1 连接，修复引号转义ignoreregex = &quot;\&quot;(remote|client)\&quot;:\&quot;127\.0\.0\.1:&quot;EOF</code></pre><h4 id="%E7%9B%91%E7%8B%B1%E9%85%8D%E7%BD%AE-3" tabindex="-1">监狱配置</h4><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/mongodb.conf &lt;&lt;&#39;EOF&#39;[mongodb]enabled = trueport = 27017filter = mongodblogpath = /www/server/mongodb/log/config.logmaxretry = 5findtime = 600bantime = 86400action = iptables[name=MongoDB, port=27017, protocol=tcp]ignoreip = 127.0.0.1EOF</code></pre><ul><li><strong>防护对象</strong>：MongoDB 27017 账户暴力破解</li><li><strong>⚠️ 注意</strong>：MongoDB 需开启权限认证，无密码登录不会产生认证失败日志</li></ul><hr /><h3 id="2.6-recidive-%E6%83%AF%E7%8A%AF%E5%8A%A0%E9%87%8D%E5%B0%81%E7%A6%81-%E2%AD%90-%E6%8E%A8%E8%8D%90" tabindex="-1">2.6 Recidive 惯犯加重封禁 ⭐ 推荐</h3><blockquote><p>利用 Fail2ban 原生自带的 <code>recidive</code> 过滤器，无需自定义 filter。<br /><strong>规则</strong>：30 天内累计被任意监狱封禁 3 次的 IP，自动升级为<strong>永久封禁</strong>。</p></blockquote><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/recidive.conf &lt;&lt;&#39;EOF&#39;[recidive]enabled  = truefilter   = recidivelogpath  = /var/log/fail2ban.logfindtime = 2592000maxretry = 3bantime  = -1banaction = iptables-multiportbanaction_allports = trueignoreip = 127.0.0.1EOF</code></pre><table><thead><tr><th>参数</th><th>值</th><th>含义</th></tr></thead><tbody><tr><td><code>findtime</code></td><td>2592000</td><td>30 天统计窗口</td></tr><tr><td><code>maxretry</code></td><td>3</td><td>累计被封 3 次触发</td></tr><tr><td><code>bantime</code></td><td>-1</td><td><strong>永久封禁</strong></td></tr></tbody></table><hr /><h2 id="%E4%B8%89%E3%80%81%E9%83%A8%E7%BD%B2%E6%A0%A1%E9%AA%8C-%26-%E7%94%9F%E6%95%88" tabindex="-1">三、部署校验 &amp; 生效</h2><h3 id="3.1-%E5%85%A8%E5%B1%80%E8%AF%AD%E6%B3%95%E6%A0%A1%E9%AA%8C" tabindex="-1">3.1 全局语法校验</h3><pre><code class="language-bash">fail2ban-client -t</code></pre><h3 id="3.2-%E7%83%AD%E5%8A%A0%E8%BD%BD%E8%A7%84%E5%88%99%EF%BC%88%E4%B8%8D%E6%B8%85%E9%99%A4%E7%8E%B0%E6%9C%89%E5%B0%81%E7%A6%81%EF%BC%89" tabindex="-1">3.2 热加载规则（不清除现有封禁）</h3><pre><code class="language-bash">fail2ban-client reload</code></pre><h3 id="3.3-%E6%9F%A5%E7%9C%8B%E5%85%A8%E9%83%A8%E7%9B%91%E7%8B%B1%E7%8A%B6%E6%80%81" tabindex="-1">3.3 查看全部监狱状态</h3><pre><code class="language-bash"># 汇总查看所有启用监狱fail2ban-client status# 逐个查看详情fail2ban-client status sshdfail2ban-client status ssl-protectfail2ban-client status mysqlfail2ban-client status redisfail2ban-client status mongodbfail2ban-client status recidive</code></pre><blockquote><p>✅ <strong>正常表现</strong>：日志路径正常显示，Failed/Banned 初始为 0，仅统计重载后新增日志，历史旧日志不计数。</p></blockquote><h3 id="3.4-%E5%85%A8%E9%87%8F%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E6%B5%8B%E8%AF%95%EF%BC%88%E8%BF%81%E7%A7%BB%E9%83%A8%E7%BD%B2%E5%BF%85%E8%B7%91%EF%BC%89" tabindex="-1">3.4 全量正则匹配测试（迁移部署必跑）</h3><pre><code class="language-bash"># SSHfail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf# Nginx SSL 扫描fail2ban-regex /www/wwwlogs/nginx_error.log /etc/fail2ban/filter.d/ssl-fail.conf# MySQLfail2ban-regex /www/server/data/hcss-ecs-695e.err /etc/fail2ban/filter.d/mysql.conf# Redisfail2ban-regex /www/server/redis/redis.log /etc/fail2ban/filter.d/redis.conf# MongoDBfail2ban-regex /www/server/mongodb/log/config.log /etc/fail2ban/filter.d/mongodb.conf</code></pre><table><thead><tr><th>结果</th><th>含义</th></tr></thead><tbody><tr><td>Failregex 总数 &gt; 0</td><td>规则正常抓取攻击日志</td></tr><tr><td>Failregex = 0</td><td>日志路径错误 / 暂无对应攻击日志</td></tr></tbody></table><hr /><h2 id="%E5%9B%9B%E3%80%81%E7%BB%9F%E4%B8%80%E8%BF%90%E7%BB%B4%E7%AE%A1%E7%90%86" tabindex="-1">四、统一运维管理</h2><h3 id="4.1-%E5%85%A8%E5%B1%80%E6%A6%82%E8%A7%88" tabindex="-1">4.1 全局概览</h3><pre><code class="language-bash"># 服务是否存活systemctl status fail2ban# 所有监狱运行状态fail2ban-client status# 全局所有被封禁 IP（所有监狱合并）fail2ban-client banned# 实时全局监控所有封禁行为tail -f /var/log/fail2ban.log</code></pre><p><strong>日志标识区分</strong>：</p><table><thead><tr><th>日志关键字</th><th>对应监狱</th><th>含义</th></tr></thead><tbody><tr><td><code>[sshd] Ban</code></td><td>SSH 防护</td><td>SSH 密码爆破封禁</td></tr><tr><td><code>[ssl-protect] Ban</code></td><td>SSL 防护</td><td>恶意 SSL 扫描 IP 封禁</td></tr><tr><td><code>[mysql] Ban</code></td><td>MySQL 防护</td><td>数据库爆破封禁</td></tr><tr><td><code>[redis] Ban</code></td><td>Redis 防护</td><td>未授权/密码爆破封禁</td></tr><tr><td><code>[mongodb] Ban</code></td><td>MongoDB 防护</td><td>账户暴力破解封禁</td></tr><tr><td><code>[recidive] Ban</code></td><td>惯犯防护</td><td>多次违规永久封禁</td></tr></tbody></table><h3 id="4.2-%E5%88%86%E7%9B%91%E7%8B%B1%E6%93%8D%E4%BD%9C" tabindex="-1">4.2 分监狱操作</h3><h4 id="%E6%9F%A5%E7%9C%8B%E5%8D%95%E4%B8%AA%E7%9B%91%E7%8B%B1%E8%AF%A6%E6%83%85" tabindex="-1">查看单个监狱详情</h4><pre><code class="language-bash">fail2ban-client status &lt;监狱名&gt; | grep Banned</code></pre><h4 id="%E8%A7%A3%E5%B0%81%E8%AF%AF%E5%B0%81-ip" tabindex="-1">解封误封 IP</h4><pre><code class="language-bash">fail2ban-client set sshd unbanip 1.2.3.4fail2ban-client set ssl-protect unbanip 1.2.3.4fail2ban-client set mysql unbanip 1.2.3.4fail2ban-client set redis unbanip 1.2.3.4fail2ban-client set mongodb unbanip 1.2.3.4</code></pre><h4 id="%E6%89%8B%E5%8A%A8%E4%B8%B4%E6%97%B6%E5%B0%81%E7%A6%81%E6%B5%8B%E8%AF%95-ip" tabindex="-1">手动临时封禁测试 IP</h4><pre><code class="language-bash">fail2ban-client set sshd banip 1.2.3.4fail2ban-client set ssl-protect banip 1.2.3.4fail2ban-client set mysql banip 1.2.3.4fail2ban-client set redis banip 1.2.3.4fail2ban-client set mongodb banip 1.2.3.4</code></pre><h3 id="4.3-%E6%89%B9%E9%87%8F%E9%85%8D%E7%BD%AE%E4%BF%AE%E6%94%B9" tabindex="-1">4.3 批量配置修改</h3><h4 id="%E6%94%BE%E5%AE%BD%E5%A4%B1%E8%B4%A5%E6%AC%A1%E6%95%B0%E4%B8%BA-10-%E6%AC%A1" tabindex="-1">放宽失败次数为 10 次</h4><pre><code class="language-bash">sed -i &#39;s/maxretry = 5/maxretry = 10/&#39; /etc/fail2ban/jail.d/sshd.confsed -i &#39;s/maxretry = 5/maxretry = 10/&#39; /etc/fail2ban/jail.d/ssl-protect.confsed -i &#39;s/maxretry = 5/maxretry = 10/&#39; /etc/fail2ban/jail.d/mysql.confsed -i &#39;s/maxretry = 5/maxretry = 10/&#39; /etc/fail2ban/jail.d/redis.confsed -i &#39;s/maxretry = 5/maxretry = 10/&#39; /etc/fail2ban/jail.d/mongodb.conffail2ban-client reload</code></pre><h4 id="%E7%BC%A9%E7%9F%AD%E5%B0%81%E7%A6%81%E6%97%B6%E9%95%BF%E4%B8%BA-1-%E5%B0%8F%E6%97%B6" tabindex="-1">缩短封禁时长为 1 小时</h4><pre><code class="language-bash">sed -i &#39;s/bantime = 86400/bantime = 3600/&#39; /etc/fail2ban/jail.d/sshd.confsed -i &#39;s/bantime = 86400/bantime = 3600/&#39; /etc/fail2ban/jail.d/ssl-protect.confsed -i &#39;s/bantime = 86400/bantime = 3600/&#39; /etc/fail2ban/jail.d/mysql.confsed -i &#39;s/bantime = 86400/bantime = 3600/&#39; /etc/fail2ban/jail.d/redis.confsed -i &#39;s/bantime = 86400/bantime = 3600/&#39; /etc/fail2ban/jail.d/mongodb.conffail2ban-client reload</code></pre><h3 id="4.4-%E7%99%BD%E5%90%8D%E5%8D%95%E7%AE%A1%E7%90%86" tabindex="-1">4.4 白名单管理</h3><h4 id="%E6%89%B9%E9%87%8F%E6%96%B0%E5%A2%9E%E7%99%BD%E5%90%8D%E5%8D%95-ip%EF%BC%88%E6%89%80%E6%9C%89%E7%9B%91%E7%8B%B1%E5%90%8C%E6%AD%A5%EF%BC%89" tabindex="-1">批量新增白名单 IP（所有监狱同步）</h4><pre><code class="language-bash">NEW_IP=&quot;123.45.67.89&quot;sed -i &quot;/^ignoreip/s/$/ $NEW_IP/&quot; /etc/fail2ban/jail.d/sshd.confsed -i &quot;/^ignoreip/s/$/ $NEW_IP/&quot; /etc/fail2ban/jail.d/ssl-protect.confsed -i &quot;/^ignoreip/s/$/ $NEW_IP/&quot; /etc/fail2ban/jail.d/mysql.confsed -i &quot;/^ignoreip/s/$/ $NEW_IP/&quot; /etc/fail2ban/jail.d/redis.confsed -i &quot;/^ignoreip/s/$/ $NEW_IP/&quot; /etc/fail2ban/jail.d/mongodb.conffail2ban-client reload</code></pre><blockquote><p>⚠️ <strong>本机被误封？</strong> 所有 jail 的 <code>ignoreip</code> 必须全部填入本机公网 IP，<strong>缺一不可</strong>。</p></blockquote><h3 id="4.5-%E6%9F%A5%E7%9C%8B%E5%90%84%E7%9B%91%E7%8B%B1%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6" tabindex="-1">4.5 查看各监狱配置文件</h3><pre><code class="language-bash">cat /etc/fail2ban/jail.d/sshd.confcat /etc/fail2ban/jail.d/ssl-protect.confcat /etc/fail2ban/jail.d/mysql.confcat /etc/fail2ban/jail.d/redis.confcat /etc/fail2ban/jail.d/mongodb.conf</code></pre><h3 id="4.6-%E9%87%8D%E7%BD%AE%E5%8D%95%E4%B8%AA%E7%9B%91%E7%8B%B1%E9%85%8D%E7%BD%AE%EF%BC%88%E5%BC%82%E5%B8%B8%E9%87%8D%E5%BB%BA%EF%BC%89" tabindex="-1">4.6 重置单个监狱配置（异常重建）</h3><p>以 Redis 为例：</p><pre><code class="language-bash">rm -f /etc/fail2ban/filter.d/redis.confrm -f /etc/fail2ban/jail.d/redis.conf# 重新执行对应部署步骤重建配置fail2ban-client reload</code></pre><hr /><h2 id="%E4%BA%94%E3%80%81%E6%94%BB%E5%87%BB%E6%97%A5%E5%BF%97%E7%BB%9F%E8%AE%A1" tabindex="-1">五、攻击日志统计</h2><h3 id="5.1-%E5%8E%86%E5%8F%B2%E5%B0%81%E7%A6%81%E7%BB%9F%E8%AE%A1" tabindex="-1">5.1 历史封禁统计</h3><pre><code class="language-bash"># 统计历史所有被 Ban 的攻击 IP + 次数（按次数倒序）grep &quot;Ban&quot; /var/log/fail2ban.log | awk &#39;{print $NF}&#39; | sort | uniq -c | sort -nr</code></pre><h3 id="5.2-%E4%BB%8A%E6%97%A5%E5%B0%81%E7%A6%81%E8%AE%B0%E5%BD%95" tabindex="-1">5.2 今日封禁记录</h3><pre><code class="language-bash">grep $(date +%Y-%m-%d) /var/log/fail2ban.log | grep Ban</code></pre><h3 id="5.3-ssh-%E5%8E%9F%E7%94%9F%E5%A4%B1%E8%B4%A5%E7%99%BB%E5%BD%95%E6%97%A5%E5%BF%97" tabindex="-1">5.3 SSH 原生失败登录日志</h3><pre><code class="language-bash">grep Failed /var/log/auth.log</code></pre><h3 id="5.4-ssl-%E6%94%BB%E5%87%BB%E6%97%A5%E5%BF%97%E6%8E%92%E6%9F%A5" tabindex="-1">5.4 SSL 攻击日志排查</h3><pre><code class="language-bash"># 检索所有 SSL 握手失败历史记录grep -i &quot;ssl.*failed&quot; /www/wwwlogs/nginx_error.log# 实时监控新增 SSL 报错tail -f /www/wwwlogs/nginx_error.log | grep -i ssl</code></pre><hr /><h2 id="%E5%85%AD%E3%80%81%E6%8E%92%E9%94%99%E6%8C%87%E5%8D%97" tabindex="-1">六、排错指南</h2><h3 id="6.1-%E9%80%9A%E7%94%A8%E6%95%85%E9%9A%9C" tabindex="-1">6.1 通用故障</h3><table><thead><tr><th>现象</th><th>原因</th><th>解决方法</th></tr></thead><tbody><tr><td>本机被误封</td><td>白名单不全</td><td>所有 jail 的 <code>ignoreip</code> 都要加本机公网 IP</td></tr><tr><td>正则匹配数为 0</td><td>日志路径 / 权限 / 无对应报错</td><td>核对路径、权限、日志内容</td></tr><tr><td>修改配置不生效</td><td>未重载</td><td>执行 <code>fail2ban-client reload</code></td></tr><tr><td>status 提示找不到 filter/jail</td><td>文件名写错 / 语法错误</td><td>用 <code>fail2ban-client -t</code> 定位报错</td></tr></tbody></table><h3 id="6.2-%E5%90%84%E7%9B%91%E7%8B%B1%E4%B8%93%E5%B1%9E%E6%95%85%E9%9A%9C" tabindex="-1">6.2 各监狱专属故障</h3><table><thead><tr><th>监狱</th><th>常见问题</th><th>排查方法</th></tr></thead><tbody><tr><td><strong>MySQL</strong></td><td>匹配无数据但日志有 Access denied</td><td>未开启 <code>log_error_verbosity=3</code> 或日志权限不足</td></tr><tr><td><strong>MySQL</strong></td><td>日志路径报错</td><td>执行 <code>grep log_error /etc/my.cnf</code> 获取真实文件名</td></tr><tr><td><strong>SSL 防护</strong></td><td>正常用户频繁误封</td><td>调高 <code>maxretry</code> 至 10~15</td></tr><tr><td><strong>SSL 防护</strong></td><td>封禁后仍可访问网站</td><td>确认 <code>banaction_allports = true</code> 存在</td></tr><tr><td><strong>Redis</strong></td><td>匹配为 0</td><td>宝塔 Redis 默认关闭日志，面板开启即可</td></tr><tr><td><strong>MongoDB</strong></td><td>匹配为 0</td><td>未开启权限认证，无密码登录不产生失败日志</td></tr></tbody></table><hr /><h2 id="%E4%B8%83%E3%80%81%E6%AF%8F%E6%97%A5%E5%B7%A1%E6%A3%80%E6%9C%80%E7%AE%80%E6%B5%81%E7%A8%8B" tabindex="-1">七、每日巡检最简流程</h2><pre><code class="language-bash"># 1. 检查服务运行状态systemctl status fail2ban# 2. 汇总所有监狱状态fail2ban-client status# 3. 列出所有被封禁 IPfail2ban-client banned# 4. 查看今日新增封禁记录grep $(date +%Y-%m-%d) /var/log/fail2ban.log | grep Ban# 5. 实时跟踪攻击拦截日志tail -f /var/log/fail2ban.log</code></pre><hr /><h2 id="%E5%85%AB%E3%80%81%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%81%E7%A7%BB%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95" tabindex="-1">八、服务器迁移检查清单</h2><ul class="contains-task-list"><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>1. 安装服务</strong></p><ul><li>CentOS：<code>yum install fail2ban -y</code></li><li>Debian/Ubuntu：<code>apt install fail2ban -y</code></li></ul></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>2. 复制过滤器文件至 <code>/etc/fail2ban/filter.d/</code></strong></p><ul><li>ssl-fail.conf、mysql.conf、redis.conf、mongodb.conf（sshd 为系统内置）</li></ul></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>3. 复制监狱配置至 <code>/etc/fail2ban/jail.d/</code></strong></p><ul><li>sshd.conf、ssl-protect.conf、mysql.conf、redis.conf、mongodb.conf、recidive.conf</li></ul></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>4. 统一修改全部 jail 的 <code>ignoreip</code></strong> 为本机公网 IP、内网网段</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>5. 修正 mysql.conf 内 <code>logpath</code></strong> 为当前服务器 MySQL 错误日志文件名</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>6. MySQL 执行前置优化脚本</strong> 开启详细错误日志</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>7. 校验全局语法</strong> <code>fail2ban-client -t</code></p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>8. 热加载规则</strong> <code>fail2ban-client reload</code></p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>9. 查看状态</strong> 确认所有监狱正常加载</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>10. 逐条执行 <code>fail2ban-regex</code></strong> 校验所有过滤器匹配正常</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> <strong>11. 手动 ban 一个测试 IP</strong> 验证 iptables 封禁功能生效</p></li></ul><hr /><h2 id="%E4%B9%9D%E3%80%81%E5%85%B3%E9%94%AE%E8%87%AA%E6%9F%A5%E5%88%A4%E6%96%AD%E6%A0%87%E5%87%86" tabindex="-1">九、关键自查判断标准</h2><table><thead><tr><th>现象</th><th>判断</th></tr></thead><tbody><tr><td><code>fail2ban-client status &lt;jail&gt;</code> 报错找不到 filter/jail</td><td>配置文件缺失 / 文件名写错</td></tr><tr><td><code>fail2ban-regex</code> 匹配数为 0</td><td>日志路径错误 / 日志无对应失败记录</td></tr><tr><td>匹配数几千，但 Total failed 始终 0</td><td>均为历史旧日志，仅新产生报错才会触发封禁</td></tr><tr><td>正常运行但频繁误封自己</td><td>检查 <code>ignoreip</code> 是否包含公网 / 内网网段</td></tr></tbody></table><hr /><h2 id="%E9%99%84%E5%BD%95%EF%BC%9A%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%B7%AF%E5%BE%84%E9%80%9F%E6%9F%A5" tabindex="-1">附录：配置文件路径速查</h2><table><thead><tr><th>类型</th><th>路径</th></tr></thead><tbody><tr><td>监狱配置</td><td><code>/etc/fail2ban/jail.d/*.conf</code></td></tr><tr><td>过滤器配置</td><td><code>/etc/fail2ban/filter.d/*.conf</code></td></tr><tr><td>Fail2ban 日志</td><td><code>/var/log/fail2ban.log</code></td></tr><tr><td>SSH 认证日志</td><td><code>/var/log/auth.log</code></td></tr><tr><td>Nginx 错误日志</td><td><code>/www/wwwlogs/nginx_error.log</code></td></tr><tr><td>MySQL 错误日志</td><td><code>/www/server/data/hcss-ecs-695e.err</code></td></tr><tr><td>Redis 日志</td><td><code>/www/server/redis/redis.log</code></td></tr><tr><td>MongoDB 日志</td><td><code>/www/server/mongodb/log/config.log</code></td></tr></tbody></table><hr /><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/serve-blog/2026/2026-08/server-safety-fal2ban-blog.md" target="_blank">本文 github 仓库地址</a>，为我点一颗 Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[fx67ll 服务器运维手册]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/server-operation" />
                <id>tag:https://fx67ll.xyz,2026-08-06:server-operation</id>
                <published>2026-08-06T19:44:37+08:00</published>
                <updated>2026-08-07T18:39:30+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="00-%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%90%E7%BB%B4%E6%89%8B%E5%86%8C%EF%BC%88%E6%B1%87%E6%80%BB%E7%89%88%EF%BC%89" tabindex="-1">00-服务器运维手册（汇总版）</h1><hr /><p><strong>最后更新时间：2026年8月6日</strong></p><blockquote><p><strong>汇总清单手册地址：</strong></p></blockquote><ul><li>fx67llLinux\server-note\operation-note\00-服务器运维手册（汇总版）.md</li><li>fx67llLinux\server-note\operation-note\<a href="http://xn--01--si3ez2bf13bhscs9wkzbq7w029g.md" target="_blank">01-应急重启检查清单.md</a></li><li>fx67llLinux\server-note\operation-note\<a href="http://xn--02--7w2e65a58kfm9apobk2cq7wj65acv8c.md" target="_blank">02-服务器迁移检查清单.md</a></li><li>fx67llLinux\server-note\operation-note\<a href="http://xn--03--gw1e31g79s37lphkk1bq7wlw1i.md" target="_blank">03-安全防护检查清单.md</a></li></ul><hr /><blockquote><p><strong>个人服务器运维全记录</strong>，涵盖应急重启、服务迁移、安全防护三大场景<br /><strong>使用方式</strong>：左侧目录跳转，或按场景快速定位</p></blockquote><hr /><h2 id="%F0%9F%93%8D-%E5%BF%AB%E9%80%9F%E5%AF%BC%E8%88%AA" tabindex="-1">📍 快速导航</h2><table><thead><tr><th>场景</th><th>跳转</th><th>触发频率</th></tr></thead><tbody><tr><td>🔥 服务器炸了 / 重启后恢复</td><td>→ <a href="#%E7%AC%AC%E4%B8%80%E7%AF%87%E5%BA%94%E6%80%A5%E9%87%8D%E5%90%AF">第一篇：应急重启</a></td><td>偶尔</td></tr><tr><td>🚚 换服务器 / 迁移数据</td><td>→ <a href="#%E7%AC%AC%E4%BA%8C%E7%AF%87%E6%9C%8D%E5%8A%A1%E8%BF%81%E7%A7%BB">第二篇：服务迁移</a></td><td>每年 1~2 次（薅新用户优惠）</td></tr><tr><td>🛡️ 被攻击了 / 安全加固</td><td>→ <a href="#%E7%AC%AC%E4%B8%89%E7%AF%87%E5%AE%89%E5%85%A8%E9%98%B2%E6%8A%A4">第三篇：安全防护</a></td><td>新服务器必做一次</td></tr></tbody></table><hr /><h1 id="%E7%AC%AC%E4%B8%80%E7%AF%87%EF%BC%9A%E5%BA%94%E6%80%A5%E9%87%8D%E5%90%AF" tabindex="-1">第一篇：应急重启</h1><blockquote><p><strong>适用场景</strong>：服务器宕机、重启后、服务异常挂掉，需要按顺序恢复<br /><strong>核心原则</strong>：先网络（防火墙），再入口（Nginx），再数据（数据库），最后应用</p></blockquote><hr /><h2 id="1.1-%E9%87%8D%E5%90%AF%E9%A1%BA%E5%BA%8F%E6%80%BB%E8%A7%88" tabindex="-1">1.1 重启顺序总览</h2><pre><code class="language-">防火墙 → Nginx → Tomcat(Jenkins) → Node → Halo</code></pre><blockquote><p>⚠️ <strong>注意</strong>：MySQL、Redis、MongoDB 默认关闭，按需开启</p></blockquote><hr /><h2 id="1.2-%E6%A0%B8%E5%BF%83%E6%9C%8D%E5%8A%A1%E9%80%90%E9%A1%B9%E9%87%8D%E5%90%AF" tabindex="-1">1.2 核心服务逐项重启</h2><h3 id="%E2%91%A0-%E9%98%B2%E7%81%AB%E5%A2%99" tabindex="-1">① 防火墙</h3><pre><code class="language-bash">service iptables start</code></pre><p><strong>验证</strong>：</p><pre><code class="language-bash">service iptables status</code></pre><blockquote><p>💡 <strong>避坑</strong>：防火墙没开的话，外部所有端口都访问不了，但本地可以。先确认防火墙状态再排查其他。</p></blockquote><hr /><h3 id="%E2%91%A1-nginx" tabindex="-1">② Nginx</h3><pre><code class="language-bash">nginx</code></pre><p><strong>验证</strong>：浏览器访问首页，或</p><pre><code class="language-bash">curl localhost</code></pre><blockquote><p>💡 <strong>避坑</strong>：如果 nginx 启动失败，大概率是配置文件语法错，用 <code>nginx -t</code> 检查。</p></blockquote><hr /><h3 id="%E2%91%A2-tomcat%EF%BC%88%E8%B7%91-jenkins%EF%BC%89" tabindex="-1">③ Tomcat（跑 Jenkins）</h3><pre><code class="language-bash">cd /usr/soft/install/apache-tomcat-9.0.7/bin./startup.sh</code></pre><p><strong>验证</strong>：访问 <code>IP:8080</code> 能看到 Tomcat 主页</p><blockquote><p>💡 <strong>避坑</strong>：启动前确保 8080 端口没被占用，用 <code>netstat -tlnp | grep 8080</code> 检查。</p></blockquote><hr /><h3 id="%E2%91%A3-node-%E6%9C%8D%E5%8A%A1" tabindex="-1">④ Node 服务</h3><pre><code class="language-bash">cd /usr/node/JDSMSpm2 start app.js --name=&quot;fx67llNode&quot;</code></pre><p><strong>验证</strong>：</p><pre><code class="language-bash">pm2 list</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>确保 <code>.env</code> 环境变量文件存在且配置正确</li><li>如果迁移了服务器，记得检查 MongoDB 连接地址是否指向新服务器</li><li>更新环境变量后用 <code>pm2 restart &lt;应用名&gt; --update-env</code> 重启</li></ul></blockquote><hr /><h3 id="%E2%91%A4-halo-%E5%8D%9A%E5%AE%A2" tabindex="-1">⑤ Halo 博客</h3><pre><code class="language-bash">sudo systemctl start halo</code></pre><p><strong>验证</strong>：</p><pre><code class="language-bash">sudo systemctl status halo</code></pre><p><strong>其他命令</strong>：</p><pre><code class="language-bash">sudo systemctl stop halo       # 停止sudo systemctl restart halo    # 重启sudo systemctl enable halo     # 开机自启</code></pre><p><strong>配置文件位置</strong>：<code>~/.halo/application.yaml</code></p><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>H2 数据库控制台平时关闭（<code>enabled: false</code>），需要调试时再开</li><li><code>web-allow-others</code> 也保持 <code>false</code>，避免外部访问数据库</li><li>缓存模式选 <code>memory</code> 即可，小站够用</li></ul></blockquote><hr /><h2 id="1.3-%E9%9D%9E%E5%B8%B8%E6%80%81%E6%9C%8D%E5%8A%A1%EF%BC%88%E5%B7%B2%E5%85%B3%E9%97%AD%EF%BC%8C%E6%8C%89%E9%9C%80%E5%BC%80%E5%90%AF%EF%BC%89" tabindex="-1">1.3 非常态服务（已关闭，按需开启）</h2><blockquote><p>这些服务都已在前台应用集合服务器上关闭，<strong>一般不需要开启</strong>，特殊情况才用</p></blockquote><h3 id="mysql-5.7" tabindex="-1">MySQL 5.7</h3><table><thead><tr><th>操作</th><th>命令</th></tr></thead><tbody><tr><td>状态</td><td><code>service mysql status</code></td></tr><tr><td>开启</td><td><code>service mysql start</code></td></tr><tr><td>关闭</td><td><code>service mysql stop</code></td></tr></tbody></table><ul><li><strong>端口</strong>：3306</li><li><strong>防火墙规则</strong>：<code>-A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -j ACCEPT</code></li></ul><h3 id="mysql-8.0" tabindex="-1">MySQL 8.0</h3><table><thead><tr><th>操作</th><th>命令</th></tr></thead><tbody><tr><td>状态</td><td><code>service mysql80 status</code></td></tr><tr><td>开启</td><td><code>service mysql80 start</code></td></tr><tr><td>关闭</td><td><code>service mysql80 stop</code></td></tr></tbody></table><ul><li><strong>端口</strong>：3307</li></ul><h3 id="redis-6.2" tabindex="-1">Redis 6.2</h3><p><strong>开启</strong>：</p><pre><code class="language-bash">cd /usr/soft/install/mongodb-linux-x86_64-rhel70-4.4.4/binredis-server redis.conf</code></pre><p><strong>关闭</strong>：</p><pre><code class="language-bash">redis-cli -h 127.0.0.1 -p 6379 -a 你的redis密码# 进入后执行shutdown</code></pre><ul><li><strong>端口</strong>：6379</li><li>显示 <code>not connected</code> 即为关闭成功</li></ul><h3 id="mongodb" tabindex="-1">MongoDB</h3><p><strong>开启</strong>：</p><pre><code class="language-bash">cd /usr/soft/install/mongodb-linux-x86_64-rhel70-4.4.4/bin./mongod -f mongodb.conf</code></pre><ul><li><strong>端口</strong>：27017</li></ul><blockquote><p>⚠️ <strong>警告</strong>：MongoDB 炸了貌似只能重装，目前还不会运维。没事别乱动。</p></blockquote><hr /><h2 id="1.4-%E9%98%B2%E7%81%AB%E5%A2%99%E7%AB%AF%E5%8F%A3%E7%AE%A1%E7%90%86" tabindex="-1">1.4 防火墙端口管理</h2><h3 id="%E5%BC%80%E6%94%BE%E6%96%B0%E7%AB%AF%E5%8F%A3%E6%B5%81%E7%A8%8B" tabindex="-1">开放新端口流程</h3><pre><code class="language-bash"># 1. 进入配置目录cd /etc/sysconfig# 2. 确认 iptables 文件存在ls iptables# 3. 编辑配置vim iptables# 按下 i 进入编辑模式，在已有端口规则下面添加：# -A INPUT -p tcp -m state --state NEW -m tcp --dport 端口号 -j ACCEPT# 按 Esc，输入 :wq 保存退出# 4. 重启生效service iptables restart</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>每开一个应用端口，一定要同步开防火墙端口，否则外部访问不了</li><li>Ubuntu 也可以用 <code>sudo ufw allow 端口号</code>，但 iptables 方式更通用</li><li>宝塔面板还有一层安全组，两边都要检查</li></ul></blockquote><hr /><h2 id="1.5-%E9%87%8D%E8%A6%81%E7%9B%AE%E5%BD%95%E9%80%9F%E6%9F%A5" tabindex="-1">1.5 重要目录速查</h2><table><thead><tr><th>路径</th><th>用途</th></tr></thead><tbody><tr><td><code>/usr/soft</code></td><td>各类软件安装包和解压包</td></tr><tr><td><code>/usr/share/nginx</code></td><td>Nginx 代理的静态文件地址</td></tr><tr><td><code>/etc/nginx</code></td><td>Nginx 配置文件目录</td></tr><tr><td><code>/etc/ssl</code></td><td>SSL 证书目录</td></tr><tr><td><code>/etc/sysconfig/iptables</code></td><td>防火墙配置文件</td></tr><tr><td><code>/usr/node</code></td><td>Node 应用目录</td></tr><tr><td><code>~/.halo</code></td><td>Halo 工作目录</td></tr><tr><td><code>/root/.jenkins</code></td><td>Jenkins 数据目录</td></tr></tbody></table><hr /><h2 id="1.6-%E9%87%8D%E5%90%AF%E5%90%8E%E9%AA%8C%E8%AF%81%E6%B8%85%E5%8D%95" tabindex="-1">1.6 重启后验证清单</h2><p>全部重启完后，逐项确认：</p><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <code>service iptables status</code> → 防火墙运行中</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 浏览器访问首页 → Nginx 正常</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <code>pm2 list</code> → Node 服务在线</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <code>IP:8080</code> → Tomcat / Jenkins 正常</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <code>IP:8090</code> → Halo 博客正常</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <code>fail2ban-client status</code> → 防爆破正常</li></ul><hr /><h1 id="%E7%AC%AC%E4%BA%8C%E7%AF%87%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%BF%81%E7%A7%BB" tabindex="-1">第二篇：服务迁移</h1><blockquote><p><strong>适用场景</strong>：旧服务器优惠到期，换新服务器薅羊毛<br /><strong>迁移原则</strong>：先基础环境，再数据，再应用，最后验证<br /><strong>预计耗时</strong>：2~4 小时（视数据量而定）</p></blockquote><hr /><h2 id="2.1-%E8%BF%81%E7%A7%BB%E5%89%8D%E5%87%86%E5%A4%87" tabindex="-1">2.1 迁移前准备</h2><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 新服务器已购买并能 SSH 登录</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 旧服务器数据已备份（宝塔一键备份 / 手动备份）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 域名解析准备好（迁移完再切）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> CDN 服务暂停（如果有）</li></ul><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>旧服务器先不要停，新的完全跑通再切流量、再销毁旧的</li><li>有 CDN 的先停 CDN，否则可能缓存旧数据影响验证</li></ul></blockquote><hr /><h2 id="2.2-%E5%9F%BA%E7%A1%80%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA" tabindex="-1">2.2 基础环境搭建</h2><h3 id="%E2%91%A0-%E5%AE%89%E8%A3%85%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF" tabindex="-1">① 安装宝塔面板</h3><pre><code class="language-bash"># 新服务器上安装宝塔# 官网获取最新安装命令，或输入 bt 命令管理bt</code></pre><h3 id="%E2%91%A1-%E9%98%B2%E7%81%AB%E5%A2%99%E7%AB%AF%E5%8F%A3%E5%BC%80%E6%94%BE" tabindex="-1">② 防火墙端口开放</h3><p><strong>服务器安全组 + 宝塔安全组，两边都要开！</strong></p><table><thead><tr><th>端口</th><th>用途</th></tr></thead><tbody><tr><td>8888</td><td>宝塔初始端口</td></tr><tr><td>3306</td><td>MySQL</td></tr><tr><td>6379</td><td>Redis</td></tr><tr><td>9897</td><td>若依服务</td></tr><tr><td>9797</td><td>超神服务</td></tr><tr><td>8090</td><td>Halo</td></tr><tr><td>8080</td><td>Tomcat / Jenkins</td></tr><tr><td>80 / 443</td><td>HTTP / HTTPS</td></tr></tbody></table><blockquote><p>💡 <strong>避坑</strong>：直接参考旧服务器依次抄一份到新服务器，别漏了。</p></blockquote><hr /><h2 id="2.3-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%81%E7%A7%BB" tabindex="-1">2.3 数据库迁移</h2><h3 id="mysql-%E8%BF%81%E7%A7%BB" tabindex="-1">MySQL 迁移</h3><pre><code class="language-bash"># 1. 宝塔安装 MySQL（选对应版本）# 2. 修改 root 密码# 3. 本地连接验证mysql -u root -p# 4. 赋予远程连接权限（用 DBeaver 等工具连接时需要）GRANT ALL PRIVILEGES ON &#96;数据库名&#96;.* TO &#39;用户名&#39;@&#39;远程IP&#39; IDENTIFIED BY &#39;密码&#39; WITH GRANT OPTION;FLUSH PRIVILEGES;# 5. 用宝塔备份功能迁移数据（最简单）# 旧服务器备份 → 下载备份文件 → 新服务器上传恢复</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>远程连接前一定要先授权，否则连不上</li><li>宝塔备份恢复最省心，不建议手动导 SQL</li></ul></blockquote><hr /><h3 id="redis-%E8%BF%81%E7%A7%BB" tabindex="-1">Redis 迁移</h3><pre><code class="language-bash"># ===== 旧服务器上 =====# 1. 宝塔安装 Redis# 2. 修改配置：#    - requirepass 设置密码#    - 注释 bind 127.0.0.1 或改为 bind 0.0.0.0#    - protected-mode 改为 no# 3. 本地 + 远程工具连接验证redis-cli# RedisDesktopManagement 也测一下# 4. 创建数据快照（BGSAVE 是异步的，不阻塞）redis-cli# 输入密码认证后BGSAVE# 5. 找到 dump.rdb 文件位置（redis.conf 中 dir 选项指定）# 6. 复制到新服务器scp /path/to/redis/dump.rdb user@newserver:/path/to/redis/# ===== 新服务器上 =====# 7. 停止 Redis 服务# 8. 把 dump.rdb 放到 Redis 工作目录# 9. 启动 Redisredis-server --port 6379 --bind 0.0.0.0 --dir /path/to/redis/ --dbfilename dump.rdb</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li><code>BGSAVE</code> 是后台保存，不阻塞；<code>SAVE</code> 是同步保存，会阻塞，生产环境用 BGSAVE</li><li>宝塔 Redis 默认关闭日志，需要的话面板里开启</li><li>一定要先停新服务器的 Redis 再覆盖 dump.rdb</li></ul></blockquote><hr /><h3 id="mongodb-%E8%BF%81%E7%A7%BB" tabindex="-1">MongoDB 迁移</h3><pre><code class="language-bash"># 1. 宝塔安装 MongoDB# 2. 修改 Node 应用的 .env 文件中 MongoDB 连接地址# 3. 用 pm2 重启并更新环境变量pm2 restart &lt;应用名称&gt; --update-env</code></pre><blockquote><p>⚠️ <strong>注意</strong>：MongoDB 迁移目前记录较少，主要靠改连接地址 + 数据同步。数据量大的话建议用 mongodump/mongorestore。</p></blockquote><hr /><h2 id="2.4-%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E8%BF%81%E7%A7%BB" tabindex="-1">2.4 后端服务迁移</h2><h3 id="java-%2F-%E8%8B%A5%E4%BE%9D%E5%90%8E%E5%8F%B0" tabindex="-1">Java / 若依后台</h3><pre><code class="language-bash"># 1. 宝塔安装 JDK 1.8 + Tomcat 9# 2. 上传服务包至 /home/ruoyi# 3. 宝塔界面配置服务并启动# 基础启动命令/usr/local/btjdk/jdk8/bin/java -jar -Xmx1024M -Xms256M /home/ruoyi/ruoyi-admin.jar --server.port=9897# 超神服务（内存不够时减配启动）/usr/bin/java \-Xms128M -Xmx512M \-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=128M \-XX:MaxDirectMemorySize=128M \-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \-Dlogging.pattern.console=&quot;%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&quot; \-Dlogging.file.max-size=100MB -Dlogging.file.max-history=7 \-jar /home/ruoyi-chaoshen/ruoyi-admin.jar \--server.port=9797 \--spring.boot.admin.enabled=false \--management.endpoints.web.exposure.include= \--spring.cache.type=none \--ruoyi.log.desensitize=false</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>若依默认端口 9897，超神服务端口 9797，别搞混</li><li>内存不够就用减配版启动命令，把堆内存、元空间都调小</li><li>超神服务关闭了 Spring Boot Admin、监控端点、缓存，进一步省内存</li></ul></blockquote><hr /><h3 id="node-%E6%9C%8D%E5%8A%A1" tabindex="-1">Node 服务</h3><pre><code class="language-bash"># 1. 从旧服务器 /usr/node 目录拷贝应用到新服务器同目录#    ⚠️ 注意：不要拷贝 node_modules 目录！# 2. 安装 Node 和 PM2#    node@14.16.0#    pm2@4.5.5# 3. 安装依赖并启动（以 NPPBE 为例，其他 Node 应用同理）cd /usr/node/NPPBEnpm installcd binpm2 start www --name=&quot;NPPBE&quot;# 4. 确认 .env 环境变量文件存在且配置正确</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>一定不要拷贝 <code>node_modules</code>，体积大且可能有平台兼容性问题</li><li><code>.env</code> 文件很重要，别漏了</li><li>迁移后检查 MongoDB 等外部连接地址是否指向新服务器</li></ul></blockquote><hr /><h3 id="jenkins%EF%BC%88tomcat-%E9%83%A8%E7%BD%B2%EF%BC%89" tabindex="-1">Jenkins（Tomcat 部署）</h3><pre><code class="language-bash"># ===== 旧服务器上备份 =====# 1. 下载 Tomcat 安装包（如果还留着就不用）#    位置：/usr/soft/sort# 2. 备份 Jenkins 数据目录（最重要！）#    /root/.jenkins 下的 jobs / plugins / secrets / users / config.xml#    这些目录和文件都要下载# 3. 备份整个 Tomcat 目录#    /usr/soft/install/apache-tomcat-9.0.7 全部下载# ===== 新服务器上部署 =====# 4. 上传 Tomcat 安装包到 /usr/soft/sort# 5. 解压tar -zxvf /usr/soft/sort/apache-tomcat-9.0.7.tar.gz -C /usr/soft/install/# 6. 配置环境变量vim /etc/profile# 在最后添加：# export CATALINA_HOME=/usr/soft/install/apache-tomcat-9.0.7# export CATALINA_BASE=/usr/soft/install/apache-tomcat-9.0.7# 保存退出# 7. 立即生效source /etc/profile# 8. 用备份的 Tomcat 目录覆盖新安装的#    （注意：先删除新 Tomcat 的 webapps/jenkins 文件夹）# 9. 启动 Tomcatcd /usr/soft/install/apache-tomcat-9.0.7/bin./startup.sh# 访问 IP:端口 看到 Tomcat 主页就算成功# 10. 先走一遍 Jenkins 新手流程，然后关闭 Tomcat# 11. 用备份的 /root/.jenkins 目录覆盖新服务器同目录# 12. 重新启动 Tomcat</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li><code>secrets</code> 文件夹覆盖后凭证能否直接用不确定，如果不行需要重新添加凭证，每个流程都要重新用新凭证</li><li>启动前一定要删干净新 Tomcat 里自带的 jenkins 文件夹，否则会冲突</li><li>环境变量改的是 <code>/etc/profile</code>（没有后缀），不是别的文件</li><li>vim 操作：按 <code>i</code> 编辑，按 <code>Esc</code> 退出编辑，按 <code>:</code> 输入命令，<code>wq</code> 保存退出</li></ul></blockquote><hr /><h3 id="halo-%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB" tabindex="-1">Halo 博客迁移</h3><h4 id="%E5%89%8D%E7%BD%AE%E5%87%86%E5%A4%87" tabindex="-1">前置准备</h4><pre><code class="language-bash"># 1. 新服务器安装 JDK 11sudo apt update &amp;&amp; sudo apt install openjdk-11-jdk   # Ubuntu/Debiansudo yum install java-11-openjdk                    # CentOS</code></pre><h4 id="%E5%AF%BC%E5%87%BA%E6%97%A7%E6%95%B0%E6%8D%AE" tabindex="-1">导出旧数据</h4><pre><code class="language-bash"># 1. 旧服务器先不要停# 2. Halo 后台 → 工具 → 导出数据（导出 data.json）# 3. xftp 导出 /home/halo/.halo 目录所有文件#    ⚠️ 注意：upload 文件夹有中文名图片，xftp 属性改为 UTF-8，否则导出报错# 4. 停 CDN（如果有）# 5. 域名解析指向新服务器 IP# 6. 新服务器开放 8090 端口</code></pre><h4 id="%E6%96%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%89%E8%A3%85" tabindex="-1">新服务器安装</h4><pre><code class="language-bash"># 1. 创建 halo 用户useradd -m halo-fx67llusermod -aG sudo halo-fx67llpasswd halo-fx67llsu - halo-fx67ll# 2. 下载 Halo 运行包mkdir ~/app &amp;&amp; cd ~/app# 1.5.2 版本官方不再维护，用 GitHub API 获取下载链接RELEASE_URL=$(curl -s https://api.github.com/repos/halo-dev/halo/releases/tags/v1.5.2 | grep &quot;browser_download_url.*halo-1.5.2.jar&quot; | cut -d &#39;&quot;&#39; -f 4)wget &quot;$RELEASE_URL&quot; -O halo.jar# 3. 创建工作目录mkdir ~/.halo &amp;&amp; cd ~/.halo# 4. 用老的配置文件（直接从老服务器拉过来）#    如果丢失了就手动重建：cat &gt; application.yaml &lt;&lt;&#39;EOF&#39;server:  port: 8090  compression:    enabled: falsespring:  datasource:    driver-class-name: org.h2.Driver    url: jdbc:h2:file:~/.halo/db/halo    username: ******    password: ******  h2:    console:      enabled: false      settings:        web-allow-others: falseEOF# 5. 测试运行cd ~/app &amp;&amp; java -jar halo.jar# 看到 Halo started successfully! 就算成功# Ctrl+C 停止测试进程</code></pre><h4 id="%E9%85%8D%E7%BD%AE%E4%B8%BA%E7%B3%BB%E7%BB%9F%E6%9C%8D%E5%8A%A1" tabindex="-1">配置为系统服务</h4><pre><code class="language-bash"># 退回 root 账户exit# 创建 systemd 服务文件cat &gt; /etc/systemd/system/halo.service &lt;&lt;&#39;EOF&#39;[Unit]Description=Halo ServiceDocumentation=https://docs.halo.runAfter=network.target[Service]Type=simpleUser=halo-fx67llGroup=halo-fx67llWorkingDirectory=/home/halo-fx67ll/appExecStart=/usr/bin/java -jar /home/halo-fx67ll/app/halo.jarRestart=alwaysStandardOutput=journal+consoleStandardError=journal+console[Install]WantedBy=multi-user.targetEOF# 重新加载 systemdsystemctl daemon-reload# 启动服务systemctl start halo# 开机自启systemctl enable halo# 查看状态systemctl status halo</code></pre><h4 id="%E5%AF%BC%E5%85%A5%E6%95%B0%E6%8D%AE" tabindex="-1">导入数据</h4><pre><code class="language-bash"># 1. 先配置好 nginx（建议先不配置 https，确保 http 能访问再说）# 2. 启动 Halo，在引导界面选择导入老数据，用之前导出的 data.json# 3. 停止 Halo，把备份的 .halo 目录所有文件覆盖到新服务器同目录#    （upload、db 等文件夹都要覆盖）# 4. 重新启动 Halo</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>建议先配 HTTP 确保能访问，再配 HTTPS</li><li>xftp 传中文文件一定要改 UTF-8 编码，否则文件名乱码或报错</li><li>导入数据和覆盖文件时，最好两边都停掉 Halo，避免数据不一致</li><li>H2 控制台和远程访问平时都关掉，安全第一</li></ul></blockquote><hr /><h2 id="2.5-%E5%89%8D%E7%AB%AF-%2F-%E9%9D%99%E6%80%81%E7%AB%99%E7%82%B9%E8%BF%81%E7%A7%BB" tabindex="-1">2.5 前端 / 静态站点迁移</h2><pre><code class="language-bash"># 1. 旧服务器 /usr/share 下的 nginx 文件夹#    整个复制到新服务器相同目录 /usr/share/nginx# 2. 逐一开放相关网站端口#    （服务器安全组 + 防火墙 + 宝塔安全组）# 3. 在 Nginx 配置目录新增对应域名的配置#    宝塔面板：/www/server/nginx/conf/vhost/nginx-fx67ll/*.conf#    主配置文件通过 include 引入：#    include /www/server/nginx/conf/vhost/nginx-fx67ll/*.conf;# 4. 每开一个端口，同步开防火墙端口sudo ufw allow 端口号# 或修改 iptables 配置后重启</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>每开一个站点就要开一个端口，别漏了防火墙</li><li>Nginx 配置通过 include 引入子目录，方便管理</li><li>403 权限不足？看下面常见问题</li></ul></blockquote><hr /><h2 id="2.6-nginx-%E9%85%8D%E7%BD%AE%E8%BF%81%E7%A7%BB" tabindex="-1">2.6 Nginx 配置迁移</h2><h3 id="%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%E5%9F%9F%E5%90%8D%E7%BB%91%E5%AE%9A-%2B-ssl" tabindex="-1">宝塔面板域名绑定 + SSL</h3><pre><code class="language-bash"># 1. 先关闭面板本身的 SSL 开关#    可视化界面关不一定成功，用 bt 命令关闭# 2. 绑定域名（可选，不绑定也能用 IP+端口访问）#    绑定后访问方式：域名+端口号# 3. Nginx 配置：80 端口代理宝塔 HTTP 地址# 4. 443 端口配置 HTTPS</code></pre><h3 id="%E5%AE%9D%E5%A1%94-nginx-%E9%85%8D%E7%BD%AE%E7%A4%BA%E4%BE%8B" tabindex="-1">宝塔 Nginx 配置示例</h3><pre><code class="language-nginx">server {    listen  80;    server_name ez13.top;    location / {        proxy_pass https://baota.fx67ll.com:10234;    }}server {    listen 443 ssl;    server_name ez13.top;    ssl_certificate     /etc/ssl/ez13.top/ez13.top.crt;    ssl_certificate_key /etc/ssl/ez13.top/ez13.top.key;    ssl_protocols       TLSv1.1 TLSv1.2 TLSv1.3;    ssl_ciphers         ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;    ssl_prefer_server_ciphers on;    ssl_session_cache   shared:SSL:1m;    ssl_session_timeout 10m;    location / {        proxy_pass https://baota.fx67ll.com:10234;    }}</code></pre><hr /><h2 id="2.7-ssl-%E8%AF%81%E4%B9%A6%E6%9B%B4%E6%96%B0" tabindex="-1">2.7 SSL 证书更新</h2><blockquote><p>SSL 证书需要<strong>每年更新</strong></p></blockquote><pre><code class="language-bash"># 1. 证书下发前选择 DNS 自动验证#    ⚠️ 记得删除之前的验证记录！# 2. 重新签发后，检查证书域名和 nginx.conf 中指向的是否一致！# 3. 证书统一存放在 /etc/ssl 目录下# 4. 更换后需要等缓存清除，不要心急！#    浏览器可能缓存旧证书，用无痕模式或清缓存测试</code></pre><blockquote><p>💡 <strong>避坑</strong>：</p><ul><li>DNS 验证前一定要删旧的验证记录，否则可能冲突</li><li>证书域名和 Nginx 配置里的 server_name 必须一致</li><li>换完证书别急，等一会儿缓存清了再验证，可能不是你的问题</li></ul></blockquote><hr /><h2 id="2.8-%E8%BF%81%E7%A7%BB%E5%90%8E%E9%AA%8C%E8%AF%81%E6%B8%85%E5%8D%95" tabindex="-1">2.8 迁移后验证清单</h2><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 宝塔面板能正常访问</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> MySQL 能连接（本地 + 远程）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> Redis 能连接（本地 + 远程）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 若依后台能访问</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 超神服务能访问</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> Node 服务正常运行</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> Jenkins 能访问，任务都在</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> Halo 博客能访问，文章和图片都在</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 所有静态站点能访问</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> HTTPS 正常，证书有效</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> Fail2Ban 已部署并运行</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 域名解析已切到新服务器</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 旧服务器可以销毁了</li></ul><hr /><h2 id="2.9-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98" tabindex="-1">2.9 常见问题</h2><h3 id="%E8%AE%BF%E9%97%AE-html-%E6%8A%A5-403-%E6%9D%83%E9%99%90%E4%B8%8D%E8%B6%B3" tabindex="-1">访问 HTML 报 403 权限不足</h3><pre><code class="language-bash"># 原因：Nginx 用户对静态文件目录没有读取权限# 解决：修改目录和文件权限chmod -R 755 /usr/share/nginx/html-8070   # 目录权限chmod 644 /usr/share/nginx/html-8070/index.html  # 文件权限</code></pre><h3 id="nginx-%E9%85%8D%E7%BD%AE%E4%B8%8D%E7%94%9F%E6%95%88" tabindex="-1">Nginx 配置不生效</h3><pre><code class="language-bash"># 检查语法nginx -t# 重载配置nginx -s reload</code></pre><hr /><h1 id="%E7%AC%AC%E4%B8%89%E7%AF%87%EF%BC%9A%E5%AE%89%E5%85%A8%E9%98%B2%E6%8A%A4" tabindex="-1">第三篇：安全防护</h1><blockquote><p><strong>适用场景</strong>：新服务器必做一次，被攻击后加固<br /><strong>核心防护</strong>：Fail2Ban 防爆破 + 系统更新 + 日常巡检</p></blockquote><hr /><h2 id="3.1-%E7%B3%BB%E7%BB%9F%E5%9F%BA%E7%A1%80%E5%8A%A0%E5%9B%BA" tabindex="-1">3.1 系统基础加固</h2><h3 id="%E4%B8%80%E9%94%AE%E6%9B%B4%E6%96%B0%E7%B3%BB%E7%BB%9F%E8%A1%A5%E4%B8%81" tabindex="-1">一键更新系统补丁</h3><pre><code class="language-bash">apt update &amp;&amp; apt upgrade -y</code></pre><h3 id="%E6%A3%80%E6%9F%A5%E7%88%86%E7%A0%B4%E7%99%BB%E5%BD%95%E8%AE%B0%E5%BD%95" tabindex="-1">检查爆破登录记录</h3><pre><code class="language-bash">grep Failed /var/log/auth.log</code></pre><blockquote><p>如果有大量失败记录，说明被爆破了，赶紧装 Fail2Ban。</p></blockquote><hr /><h2 id="3.2-fail2ban-%E9%98%B2%E7%88%86%E7%A0%B4" tabindex="-1">3.2 Fail2Ban 防爆破</h2><h3 id="%E5%AE%89%E8%A3%85" tabindex="-1">安装</h3><pre><code class="language-bash">apt update -yapt install fail2ban -y# 开机自启 + 启动systemctl enable --now fail2bansystemctl restart fail2ban</code></pre><h3 id="ssh-%E9%98%B2%E7%88%86%E7%A0%B4%EF%BC%88%E5%BF%85%E8%A3%85%EF%BC%89" tabindex="-1">SSH 防爆破（必装）</h3><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/sshd.conf &lt;&lt;&#39;EOF&#39;[sshd]enabled = trueport = sshfilter = sshdlogpath = /var/log/auth.logmaxretry = 5bantime = 86400findtime = 600ignoreip = 127.0.0.1EOF</code></pre><table><thead><tr><th>参数</th><th>值</th><th>含义</th></tr></thead><tbody><tr><td>maxretry</td><td>5</td><td>最多错 5 次</td></tr><tr><td>bantime</td><td>86400</td><td>封 24 小时</td></tr><tr><td>findtime</td><td>600</td><td>10 分钟内</td></tr><tr><td>ignoreip</td><td>127.0.0.1</td><td>白名单</td></tr></tbody></table><blockquote><p>⚠️ <strong>重要</strong>：<code>ignoreip</code> 一定要加上你自己的公网 IP，否则误封自己就麻烦了。</p></blockquote><h3 id="nginx-ssl-%E6%81%B6%E6%84%8F%E6%89%AB%E6%8F%8F%E9%98%B2%E6%8A%A4" tabindex="-1">Nginx SSL 恶意扫描防护</h3><pre><code class="language-bash"># 过滤器cat &gt; /etc/fail2ban/filter.d/ssl-fail.conf &lt;&lt;&#39;EOF&#39;[Definition]failregex = .*SSL.*failed.*client: &lt;HOST&gt;ignoreregex =EOF# 监狱cat &gt; /etc/fail2ban/jail.d/ssl-protect.conf &lt;&lt;&#39;EOF&#39;[ssl-protect]enabled = truefilter = ssl-faillogpath = /www/wwwlogs/nginx_error.logmaxretry = 5findtime = 600bantime = 86400banaction = iptables-multiportbanaction_allports = trueignoreip = 127.0.0.1EOF</code></pre><blockquote><p>💡 <code>banaction_allports = true</code> → 封禁该 IP 所有端口，避免换端口扫描</p></blockquote><h3 id="mysql-%E9%98%B2%E7%88%86%E7%A0%B4" tabindex="-1">MySQL 防爆破</h3><pre><code class="language-bash"># 前置优化：开启详细错误日志（必须做，否则匹配失效）grep log_error_verbosity /etc/my.cnf# 无输出则写入sed -i &#39;/\[mysqld\]/a log_error_verbosity = 3&#39; /etc/my.cnf/etc/init.d/mysqld restartchmod 644 /www/server/data/hcss-ecs-695e.err# 过滤器cat &gt; /etc/fail2ban/filter.d/mysql.conf &lt;&lt;&#39;EOF&#39;[INCLUDES]before = common.conf[Definition]_daemon = mysqldfailregex = ^.*Access denied for user .*@&#39;&lt;HOST&gt;&#39;ignoreregex =EOF# 监狱cat &gt; /etc/fail2ban/jail.d/mysql.conf &lt;&lt;&#39;EOF&#39;[mysql]enabled = trueport = 3306filter = mysqllogpath = /www/server/data/hcss-ecs-695e.errmaxretry = 5findtime = 600bantime = 86400action = iptables[name=MySQL, port=3306, protocol=tcp]ignoreip = 127.0.0.1EOF</code></pre><h3 id="redis-%E9%98%B2%E7%88%86%E7%A0%B4" tabindex="-1">Redis 防爆破</h3><pre><code class="language-bash"># 过滤器cat &gt; /etc/fail2ban/filter.d/redis.conf &lt;&lt;&#39;EOF&#39;[Definition]failregex = ^.*Failed password for user .* from &lt;HOST&gt;            ^.*Authentication failed for user .* from &lt;HOST&gt;            ^.*ERR invalid password from &lt;HOST&gt;            ^.*Client sent AUTH, but no password is set from &lt;HOST&gt;ignoreregex =EOF# 监狱cat &gt; /etc/fail2ban/jail.d/redis.conf &lt;&lt;&#39;EOF&#39;[redis]enabled = trueport = 6379filter = redislogpath = /www/server/redis/redis.logmaxretry = 5findtime = 600bantime = 86400action = iptables[name=Redis, port=6379, protocol=tcp]ignoreip = 127.0.0.1EOF</code></pre><h3 id="mongodb-%E9%98%B2%E7%88%86%E7%A0%B4" tabindex="-1">MongoDB 防爆破</h3><pre><code class="language-bash"># 过滤器cat &gt; /etc/fail2ban/filter.d/mongodb.conf &lt;&lt;&#39;EOF&#39;[Definition]failregex = .*&quot;msg&quot;:&quot;(Auth failed|Authentication failed|Authentication failure|login failed|Unauthorized|auth error)&quot;.*&quot;(remote|client)&quot;:&quot;&lt;HOST&gt;:\d+&quot;            .*&quot;errmsg&quot;:&quot;.*Connection reset by peer&quot;.*&quot;(remote|client)&quot;:&quot;&lt;HOST&gt;:\d+&quot;            .*&quot;Interrupted operation as its client disconnected&quot;.*&quot;(remote|client)&quot;:&quot;&lt;HOST&gt;:\d+&quot;datepattern = {&quot;t&quot;:{&quot;\$date&quot;:&quot;%%Y-%%m-%%dT%%H:%%M:%%S\.%%f\+\d{2}:\d{2}&quot;}}ignoreregex = &quot;\&quot;(remote|client)\&quot;:\&quot;127\.0\.0\.1:&quot;EOF# 监狱cat &gt; /etc/fail2ban/jail.d/mongodb.conf &lt;&lt;&#39;EOF&#39;[mongodb]enabled = trueport = 27017filter = mongodblogpath = /www/server/mongodb/log/config.logmaxretry = 5findtime = 600bantime = 86400action = iptables[name=MongoDB, port=27017, protocol=tcp]ignoreip = 127.0.0.1EOF</code></pre><h3 id="recidive-%E6%83%AF%E7%8A%AF%E5%8A%A0%E9%87%8D%E5%B0%81%E7%A6%81%EF%BC%88%E6%8E%A8%E8%8D%90%EF%BC%89" tabindex="-1">Recidive 惯犯加重封禁（推荐）</h3><blockquote><p>30 天内累计被封 3 次 → 永久封禁</p></blockquote><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/recidive.conf &lt;&lt;&#39;EOF&#39;[recidive]enabled  = truefilter   = recidivelogpath  = /var/log/fail2ban.logfindtime = 2592000maxretry = 3bantime  = -1banaction = iptables-multiportbanaction_allports = trueignoreip = 127.0.0.1EOF</code></pre><h3 id="%E7%94%9F%E6%95%88-%26-%E6%A0%A1%E9%AA%8C" tabindex="-1">生效 &amp; 校验</h3><pre><code class="language-bash"># 语法校验fail2ban-client -t# 热加载（不清除现有封禁）fail2ban-client reload# 查看所有监狱状态fail2ban-client status# 逐个查看fail2ban-client status sshdfail2ban-client status ssl-protectfail2ban-client status mysqlfail2ban-client status redisfail2ban-client status mongodbfail2ban-client status recidive</code></pre><h3 id="%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E6%B5%8B%E8%AF%95%EF%BC%88%E8%BF%81%E7%A7%BB%E5%BF%85%E8%B7%91%EF%BC%89" tabindex="-1">正则匹配测试（迁移必跑）</h3><pre><code class="language-bash">fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conffail2ban-regex /www/wwwlogs/nginx_error.log /etc/fail2ban/filter.d/ssl-fail.conffail2ban-regex /www/server/data/hcss-ecs-695e.err /etc/fail2ban/filter.d/mysql.conffail2ban-regex /www/server/redis/redis.log /etc/fail2ban/filter.d/redis.conffail2ban-regex /www/server/mongodb/log/config.log /etc/fail2ban/filter.d/mongodb.conf</code></pre><blockquote><p>Failregex &gt; 0 = 正常；= 0 = 路径错或暂无攻击日志</p></blockquote><hr /><h2 id="3.3-%E6%97%A5%E5%B8%B8%E8%BF%90%E7%BB%B4%E5%91%BD%E4%BB%A4" tabindex="-1">3.3 日常运维命令</h2><h3 id="%E5%85%A8%E5%B1%80%E6%A6%82%E8%A7%88" tabindex="-1">全局概览</h3><pre><code class="language-bash"># 服务状态systemctl status fail2ban# 所有监狱fail2ban-client status# 所有被封 IPfail2ban-client banned# 实时监控封禁日志tail -f /var/log/fail2ban.log</code></pre><h3 id="%E5%88%86%E7%9B%91%E7%8B%B1%E6%93%8D%E4%BD%9C" tabindex="-1">分监狱操作</h3><pre><code class="language-bash"># 查看单个监狱fail2ban-client status &lt;监狱名&gt;# 解封 IPfail2ban-client set sshd unbanip 1.2.3.4fail2ban-client set ssl-protect unbanip 1.2.3.4fail2ban-client set mysql unbanip 1.2.3.4fail2ban-client set redis unbanip 1.2.3.4fail2ban-client set mongodb unbanip 1.2.3.4# 手动封禁测试fail2ban-client set sshd banip 1.2.3.4</code></pre><h3 id="%E6%89%B9%E9%87%8F%E4%BF%AE%E6%94%B9" tabindex="-1">批量修改</h3><pre><code class="language-bash"># 放宽阈值到 10 次sed -i &#39;s/maxretry = 5/maxretry = 10/&#39; /etc/fail2ban/jail.d/*.conffail2ban-client reload# 缩短封禁时间为 1 小时sed -i &#39;s/bantime = 86400/bantime = 3600/&#39; /etc/fail2ban/jail.d/*.conffail2ban-client reload# 批量加白名单NEW_IP=&quot;123.45.67.89&quot;sed -i &quot;/^ignoreip/s/$/ $NEW_IP/&quot; /etc/fail2ban/jail.d/*.conffail2ban-client reload</code></pre><h3 id="%E6%94%BB%E5%87%BB%E7%BB%9F%E8%AE%A1" tabindex="-1">攻击统计</h3><pre><code class="language-bash"># 历史所有被 Ban 的 IP + 次数（倒序）grep &quot;Ban&quot; /var/log/fail2ban.log | awk &#39;{print $NF}&#39; | sort | uniq -c | sort -nr# 今日封禁记录grep $(date +%Y-%m-%d) /var/log/fail2ban.log | grep Ban# SSL 攻击日志grep -i &quot;ssl.*failed&quot; /www/wwwlogs/nginx_error.log</code></pre><hr /><h2 id="3.4-%E6%8E%92%E9%94%99%E6%8C%87%E5%8D%97" tabindex="-1">3.4 排错指南</h2><table><thead><tr><th>现象</th><th>原因</th><th>解决</th></tr></thead><tbody><tr><td>本机被误封</td><td>白名单不全</td><td>所有 jail 的 ignoreip 都加本机公网 IP</td></tr><tr><td>正则匹配 0</td><td>路径/权限/无对应日志</td><td>核对路径、权限、日志内容</td></tr><tr><td>修改不生效</td><td>没 reload</td><td><code>fail2ban-client reload</code></td></tr><tr><td>找不到 jail</td><td>文件名错/语法错</td><td><code>fail2ban-client -t</code> 定位报错</td></tr><tr><td>MySQL 匹配 0</td><td>没开详细日志</td><td><code>log_error_verbosity=3</code> + 日志权限</td></tr><tr><td>SSL 误封正常用户</td><td>阈值太低</td><td>调高 maxretry 到 10~15</td></tr><tr><td>Redis 匹配 0</td><td>日志没开</td><td>宝塔面板开启 Redis 日志</td></tr><tr><td>MongoDB 匹配 0</td><td>没开认证</td><td>无密码登录不产生失败日志</td></tr><tr><td>匹配几千但 failed 0</td><td>都是历史日志</td><td>只统计新增日志，正常</td></tr></tbody></table><hr /><h2 id="3.5-%E6%AF%8F%E6%97%A5%E5%B7%A1%E6%A3%80%E6%B5%81%E7%A8%8B" tabindex="-1">3.5 每日巡检流程</h2><pre><code class="language-bash"># 1. 服务是否存活systemctl status fail2ban# 2. 所有监狱状态fail2ban-client status# 3. 当前封禁 IP 总量fail2ban-client banned# 4. 今日新增拦截grep $(date +%Y-%m-%d) /var/log/fail2ban.log | grep Ban# 5. 实时监控（异常排查时用）tail -f /var/log/fail2ban.log</code></pre><hr /><h2 id="3.6-%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%81%E7%A7%BB%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95%EF%BC%88fail2ban-%E9%83%A8%E5%88%86%EF%BC%89" tabindex="-1">3.6 服务器迁移检查清单（Fail2Ban 部分）</h2><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 安装 fail2ban</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 复制所有 filter 文件到 <code>/etc/fail2ban/filter.d/</code></li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 复制所有 jail 文件到 <code>/etc/fail2ban/jail.d/</code></li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 所有 jail 的 <code>ignoreip</code> 改为新服务器公网 IP</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> MySQL jail 的 <code>logpath</code> 改为新主机名</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> MySQL 执行前置优化（开启详细日志）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <code>fail2ban-client -t</code> 语法校验</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <code>fail2ban-client reload</code> 热加载</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 确认所有监狱正常加载</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 逐条 <code>fail2ban-regex</code> 校验匹配正常</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 手动 ban 一个测试 IP，验证 iptables 生效</li></ul><hr /><h1 id="%E9%99%84%E5%BD%95" tabindex="-1">附录</h1><h2 id="a.-%E7%AB%AF%E5%8F%A3%E9%80%9F%E6%9F%A5%E8%A1%A8" tabindex="-1">A. 端口速查表</h2><table><thead><tr><th>端口</th><th>服务</th><th>默认开启</th></tr></thead><tbody><tr><td>22</td><td>SSH</td><td>✅</td></tr><tr><td>80</td><td>HTTP</td><td>✅</td></tr><tr><td>443</td><td>HTTPS</td><td>✅</td></tr><tr><td>8080</td><td>Tomcat / Jenkins</td><td>✅</td></tr><tr><td>8090</td><td>Halo</td><td>✅</td></tr><tr><td>3306</td><td>MySQL 5.7</td><td>❌</td></tr><tr><td>3307</td><td>MySQL 8.0</td><td>❌</td></tr><tr><td>6379</td><td>Redis</td><td>❌</td></tr><tr><td>27017</td><td>MongoDB</td><td>❌</td></tr><tr><td>9897</td><td>若依后台</td><td>✅</td></tr><tr><td>9797</td><td>超神服务</td><td>✅</td></tr><tr><td>8888</td><td>宝塔初始端口</td><td>✅（已改）</td></tr><tr><td>10234</td><td>宝塔改后端口</td><td>✅</td></tr></tbody></table><h2 id="b.-%E7%9B%AE%E5%BD%95%E9%80%9F%E6%9F%A5%E8%A1%A8" tabindex="-1">B. 目录速查表</h2><table><thead><tr><th>路径</th><th>用途</th></tr></thead><tbody><tr><td><code>/usr/soft</code></td><td>软件安装包</td></tr><tr><td><code>/usr/share/nginx</code></td><td>静态站点文件</td></tr><tr><td><code>/etc/nginx</code></td><td>Nginx 配置</td></tr><tr><td><code>/etc/ssl</code></td><td>SSL 证书</td></tr><tr><td><code>/etc/sysconfig/iptables</code></td><td>防火墙配置</td></tr><tr><td><code>/usr/node</code></td><td>Node 应用</td></tr><tr><td><code>~/.halo</code></td><td>Halo 工作目录</td></tr><tr><td><code>/root/.jenkins</code></td><td>Jenkins 数据</td></tr><tr><td><code>/etc/fail2ban</code></td><td>Fail2Ban 配置</td></tr><tr><td><code>/var/log/fail2ban.log</code></td><td>Fail2Ban 日志</td></tr><tr><td><code>/var/log/auth.log</code></td><td>SSH 认证日志</td></tr><tr><td><code>/www/wwwlogs/nginx_error.log</code></td><td>Nginx 错误日志</td></tr><tr><td><code>/www/server/data/</code></td><td>MySQL 数据目录</td></tr><tr><td><code>/www/server/redis/</code></td><td>Redis 目录</td></tr><tr><td><code>/www/server/mongodb/</code></td><td>MongoDB 目录</td></tr><tr><td><code>/home/ruoyi/</code></td><td>若依服务</td></tr><tr><td><code>/home/ruoyi-chaoshen/</code></td><td>超神服务</td></tr></tbody></table><h2 id="c.-%E6%9C%AA%E6%9D%A5-todo" tabindex="-1">C. 未来 TODO</h2><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 写一个自动重启脚本，服务器炸了后自动启动所有服务，类似 Halo 的 systemd 守护</li></ul>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[服务器入侵应急响应与安全加固全指南]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/server-ntr-record" />
                <id>tag:https://fx67ll.xyz,2026-06-03:server-ntr-record</id>
                <published>2026-06-03T16:58:32+08:00</published>
                <updated>2026-08-07T18:39:25+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%A2%AB-ntr-%E4%BA%8B%E4%BB%B6%E8%AE%B0%E5%BD%95" tabindex="-1">服务器被 NTR 事件记录</h1><p><strong>分析时间</strong>：2026-06-01<br /><strong>受影响服务器</strong>：hcss-ecs-695e<br /><strong>攻击类型</strong>：SSH密码爆破 + 加密货币挖矿木马 + 双重持久化后门</p><hr /><h2 id="%E4%B8%80%E3%80%81%E6%94%BB%E5%87%BB%E4%BA%8B%E4%BB%B6%E5%A4%8D%E7%9B%98" tabindex="-1">一、攻击事件复盘</h2><h3 id="1.1-%E6%94%BB%E5%87%BB%E6%97%B6%E9%97%B4%E7%BA%BF" tabindex="-1">1.1 攻击时间线</h3><table><thead><tr><th>时间</th><th>事件</th><th>风险等级</th></tr></thead><tbody><tr><td>2025-03-17 19:47</td><td>服务器初始化部署</td><td>-</td></tr><tr><td>2026-05-31 20:22</td><td>攻击者IP <code>220.250.52.101</code> 通过SSH密码登录<code>halo</code>用户</td><td>高危</td></tr><tr><td>2026-05-31 20:47</td><td>意大利IP <code>151.47.111.200</code> 二次登录，开始横向操作</td><td>高危</td></tr><tr><td>2026-05-31 20:59</td><td>挖矿包 <code>dota3.tar.gz</code> 写入 <code>/tmp/.X291-unix/</code> 临时目录</td><td>中危</td></tr><tr><td>2026-05-31 21:19</td><td>完整木马套件部署至 <code>/home/halo/.configrc7/</code></td><td>高危</td></tr></tbody></table><h3 id="1.2-%E6%94%BB%E5%87%BB%E6%8A%80%E6%9C%AF%E7%BB%86%E8%8A%82" tabindex="-1">1.2 攻击技术细节</h3><h4 id="%E5%85%A5%E4%BE%B5%E5%85%A5%E5%8F%A3%EF%BC%9A%E5%BC%B1%E5%AF%86%E7%A0%81ssh%E8%AE%A4%E8%AF%81" tabindex="-1">入侵入口：弱密码SSH认证</h4><ul><li>服务器未禁用SSH密码登录，<code>halo</code>账号使用弱密码或被泄露</li><li>攻击者通过自动化工具批量扫描全网开放22端口的服务器，进行字典爆破</li><li>两个攻击IP均为境外恶意节点，其中<code>151.47.111.200</code>归属意大利WIND TRE运营商</li></ul><h4 id="%E6%81%B6%E6%84%8F%E8%BD%BD%E8%8D%B7%EF%BC%9Axmr%E9%97%A8%E7%BD%97%E5%B8%81%E6%8C%96%E7%9F%BF%E6%9C%A8%E9%A9%AC" tabindex="-1">恶意载荷：XMR门罗币挖矿木马</h4><ul><li>主挖矿进程伪装成内核线程 <code>kthreadadd64</code>，并通过<code>httpd</code>进程启动以混淆视听</li><li>完整执行命令：<code>/usr/sbin/httpd .rsync/c/kthreadadd64 -t 525</code>（<code>-t</code>指定挖矿线程数）</li><li>木马套件包含：启动脚本<code>run</code>、初始化脚本<code>init01</code>、定时任务<code>crond</code>、日志清理工具<code>delsshdv</code>、停止脚本<code>stop</code>、挖矿进程管理<code>rmkwork</code></li></ul><h4 id="%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6%EF%BC%9A%E5%8F%8C%E9%87%8D%E5%90%8E%E9%97%A8%E4%BF%9D%E9%9A%9C" tabindex="-1">持久化机制：双重后门保障</h4><ol><li><strong>定时任务后门</strong>：篡改<code>halo</code>和<code>root</code>用户的crontab，每分钟检查并重启挖矿进程</li><li><strong>SSH公钥后门</strong>：在<code>~/.ssh/authorized_keys</code>中植入攻击者公钥，即使修改密码也能登录</li></ol><hr /><h2 id="%E4%BA%8C%E3%80%81%E5%BA%94%E6%80%A5%E5%93%8D%E5%BA%94%E5%A4%84%E7%BD%AE%E6%AD%A5%E9%AA%A4" tabindex="-1">二、应急响应处置步骤</h2><h3 id="%E7%AC%AC%E4%B8%80%E6%AD%A5%EF%BC%9A%E7%BD%91%E7%BB%9C%E9%9A%94%E7%A6%BB%EF%BC%88%E6%9C%80%E9%AB%98%E4%BC%98%E5%85%88%E7%BA%A7%EF%BC%89" tabindex="-1">第一步：网络隔离（最高优先级）</h3><p><strong>立即在云控制台安全组执行以下操作</strong>：</p><ul><li>仅允许你的办公IP访问22端口（SSH）</li><li>临时关闭所有非必要入站端口（80/443除外）</li><li>禁止服务器主动出站连接（防止下载更多恶意文件）</li></ul><blockquote><p>⚠️ 警告：此步骤必须最先执行，否则攻击者可能在你清理过程中重新入侵</p></blockquote><h3 id="%E7%AC%AC%E4%BA%8C%E6%AD%A5%EF%BC%9A%E7%BB%88%E6%AD%A2%E6%89%80%E6%9C%89%E6%81%B6%E6%84%8F%E8%BF%9B%E7%A8%8B" tabindex="-1">第二步：终止所有恶意进程</h3><pre><code class="language-bash"># 精确查找并杀死挖矿主进程ps aux | grep -E &quot;kthreadadd|\.rsync|httpd.*kthread&quot; | grep -v grep | awk &#39;{print $2}&#39; | xargs kill -9# 批量查杀相关进程pkill -9 -f kthreadadd64pkill -9 -f &quot;.rsync/c&quot;pkill -9 -f &quot;dota3&quot;# 确认无残留进程ps aux | grep -E &quot;kthreadadd|\.rsync&quot;</code></pre><h3 id="%E7%AC%AC%E4%B8%89%E6%AD%A5%EF%BC%9A%E5%BD%BB%E5%BA%95%E6%B8%85%E9%99%A4%E6%81%B6%E6%84%8F%E6%96%87%E4%BB%B6" tabindex="-1">第三步：彻底清除恶意文件</h3><pre><code class="language-bash"># 删除临时目录挖矿包rm -rf /tmp/.X291-unix/rm -rf /var/tmp/.X291-unix/# 删除用户目录下的木马套件rm -rf /home/halo/.configrc7/# 全局搜索可能遗漏的恶意文件find / -name &quot;dota3.tar.gz&quot; -o -name &quot;kthreadadd64&quot; -o -name &quot;.configrc7&quot; 2&gt;/dev/null</code></pre><h3 id="%E7%AC%AC%E5%9B%9B%E6%AD%A5%EF%BC%9A%E6%B8%85%E9%99%A4%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E5%90%8E%E9%97%A8" tabindex="-1">第四步：清除定时任务后门</h3><pre><code class="language-bash"># 清除所有用户的crontabcrontab -u halo -rcrontab -u root -r# 检查系统级定时任务rm -rf /etc/cron.d/*malicious*cat /etc/crontab | grep -v &quot;^#&quot;ls -la /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/# 检查用户级crontab目录ls -la /var/spool/cron/crontabs/</code></pre><h3 id="%E7%AC%AC%E4%BA%94%E6%AD%A5%EF%BC%9A%E6%B8%85%E9%99%A4ssh%E5%85%AC%E9%92%A5%E5%90%8E%E9%97%A8" tabindex="-1">第五步：清除SSH公钥后门</h3><pre><code class="language-bash"># 备份并清空halo用户的authorized_keyscp /home/halo/.ssh/authorized_keys /home/halo/.ssh/authorized_keys.bak&gt; /home/halo/.ssh/authorized_keys# 检查root用户的公钥文件&gt; /root/.ssh/authorized_keys# 修复文件权限（防止被重新写入）chmod 600 /home/halo/.ssh/authorized_keyschmod 700 /home/halo/.ssh/chown -R halo:halo /home/halo/.ssh/</code></pre><h3 id="%E7%AC%AC%E5%85%AD%E6%AD%A5%EF%BC%9Assh%E6%9C%8D%E5%8A%A1%E5%8A%A0%E5%9B%BA" tabindex="-1">第六步：SSH服务加固</h3><pre><code class="language-bash"># 修改halo用户密码（使用强密码：16位以上，包含大小写字母、数字和特殊符号）passwd halo# 编辑SSH配置文件nano /etc/ssh/sshd_config</code></pre><p>修改以下关键配置项：</p><pre><code class="language-ini"># 禁用root远程登录PermitRootLogin no# 彻底禁用密码认证（必须先确保密钥登录正常）PasswordAuthentication no# 禁用空密码登录PermitEmptyPasswords no# 限制最大认证尝试次数MaxAuthTries 3# 限制登录会话数MaxSessions 2</code></pre><p>重启SSH服务使配置生效：</p><pre><code class="language-bash">systemctl restart sshd</code></pre><blockquote><p>⚠️ 重要：在禁用密码登录前，<strong>务必先测试密钥登录是否正常</strong>，否则会导致自己无法登录服务器</p></blockquote><h3 id="%E7%AC%AC%E4%B8%83%E6%AD%A5%EF%BC%9Aip%E5%B0%81%E7%A6%81%E4%B8%8E%E9%98%B2%E7%81%AB%E5%A2%99%E9%85%8D%E7%BD%AE" tabindex="-1">第七步：IP封禁与防火墙配置</h3><ol><li><p>在云控制台安全组中永久封禁以下IP：</p><ul><li><code>220.250.52.101</code></li><li><code>151.47.111.200</code></li></ul></li><li><p>配置宝塔面板防火墙（推荐）：</p><ul><li>访问：<code>https://baota.fx67ll.com/firewall</code></li><li>添加IP黑名单，禁止上述IP所有端口访问</li><li>开启SSH防爆破功能</li></ul></li></ol><h3 id="%E7%AC%AC%E5%85%AB%E6%AD%A5%EF%BC%9A%E6%B7%B1%E5%BA%A6%E5%90%8E%E9%97%A8%E6%8E%92%E6%9F%A5" tabindex="-1">第八步：深度后门排查</h3><pre><code class="language-bash"># 1. 检查是否存在UID=0的后门账号awk -F: &#39;$3==0 &amp;&amp; $1!=&quot;root&quot;&#39; /etc/passwd# 2. 检查所有可登录用户cat /etc/passwd | grep -E &quot;/bin/bash|/bin/sh&quot; | grep -v root# 3. 检查SUID权限文件（可能被替换为后门）find /usr/bin /usr/sbin /bin /sbin -perm -4000 -type f 2&gt;/dev/null# 4. 检查动态链接库劫持（常见木马手段）cat /etc/ld.so.preloadcat /etc/ld.so.conf.d/*# 5. 检查自启动服务systemctl list-unit-files --type=service | grep enabled | grep -v &quot;@&quot;# 6. 检查rc.local自启动cat /etc/rc.localls -la /etc/profile.d/# 7. 查找带immutable锁定的顽固文件find / -type f -exec lsattr {} \; 2&gt;/dev/null | grep &#39;i&#39;# 8. 检查最近3天修改的系统文件find /etc /usr/bin /usr/sbin /bin /sbin -mtime -3 -type f 2&gt;/dev/null</code></pre><hr /><h2 id="%E4%B8%89%E3%80%81%E5%85%A8%E9%9D%A2%E5%AE%89%E5%85%A8%E9%9A%90%E6%82%A3%E8%87%AA%E6%9F%A5%E6%B8%85%E5%8D%95" tabindex="-1">三、全面安全隐患自查清单</h2><h3 id="3.1-%E8%B4%A6%E5%8F%B7%E4%B8%8E%E6%9D%83%E9%99%90%E5%AE%89%E5%85%A8" tabindex="-1">3.1 账号与权限安全</h3><pre><code class="language-bash"># 查看所有可登录用户cat /etc/passwd | grep -v nologin | grep -v false# 查看最近新增用户ls -lt /etc/shadow | head -10# 查看sudo提权用户cat /etc/sudoers | grep -v &#39;^#&#39; | grep -v &#39;^$&#39;ls /etc/sudoers.d/</code></pre><h3 id="3.2-ssh%E6%9C%8D%E5%8A%A1%E5%AE%89%E5%85%A8" tabindex="-1">3.2 SSH服务安全</h3><pre><code class="language-bash"># 查看SSH配置grep -v &#39;^#&#39; /etc/ssh/sshd_config | grep -v &#39;^$&#39;# 查看登录历史lastloglast -n 20# 查看登录失败记录grep &quot;Failed password&quot; /var/log/auth.log | tail -20</code></pre><h3 id="3.3-%E7%BD%91%E7%BB%9C%E4%B8%8E%E7%AB%AF%E5%8F%A3%E5%AE%89%E5%85%A8" tabindex="-1">3.3 网络与端口安全</h3><pre><code class="language-bash"># 查看所有监听端口ss -tulnp# 重点检查高危端口ss -tulnp | grep -E &quot;6379|3306|27017|6379|8080|9000&quot;</code></pre><blockquote><p>✅ 安全标准：除80/443外，所有服务端口不应监听<code>0.0.0.0</code>，应改为<code>127.0.0.1</code>或通过安全组限制访问</p></blockquote><h3 id="3.4-%E7%B3%BB%E7%BB%9F%E5%AE%8C%E6%95%B4%E6%80%A7%E6%A3%80%E6%9F%A5" tabindex="-1">3.4 系统完整性检查</h3><pre><code class="language-bash"># 检查关键系统文件修改时间stat /bin/ps /bin/ls /usr/bin/curl /usr/bin/wget /usr/bin/top# 对比系统安装时间，若修改时间晚于安装时间则可能被篡改</code></pre><h3 id="3.5-%E8%87%AA%E6%9F%A5%E7%BB%93%E6%9E%9C%E5%88%A4%E6%96%AD%E6%A0%87%E5%87%86" tabindex="-1">3.5 自查结果判断标准</h3><table><thead><tr><th>检查项</th><th>安全状态</th><th>风险状态</th></tr></thead><tbody><tr><td>UID=0账号</td><td>仅root用户</td><td>存在其他UID=0账号</td></tr><tr><td>authorized_keys</td><td>仅包含自己的公钥</td><td>存在陌生公钥</td></tr><tr><td>ld.so.preload</td><td>空文件</td><td>包含任何内容</td></tr><tr><td>定时任务</td><td>无陌生任务</td><td>存在未知定时任务</td></tr><tr><td>监听端口</td><td>仅80/443/22(受限)</td><td>高危端口监听0.0.0.0</td></tr><tr><td>锁定文件</td><td>无带<code>i</code>属性的可疑文件</td><td>存在系统目录外的锁定文件</td></tr></tbody></table><hr /><h2 id="%E5%9B%9B%E3%80%81fail2ban-%E9%98%B2%E7%88%86%E7%A0%B4%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97" tabindex="-1">四、Fail2Ban 防爆破部署指南</h2><p>Fail2Ban是一款开源的入侵防御工具，通过监控系统日志自动封禁恶意IP，是SSH防爆破的最佳实践。</p><h3 id="4.1-%E5%AE%89%E8%A3%85%E4%B8%8E%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE" tabindex="-1">4.1 安装与基础配置</h3><pre><code class="language-bash"># 更新系统并安装apt update -yapt install fail2ban -y# 启动服务并设置开机自启systemctl enable --now fail2bansystemctl status fail2ban</code></pre><h3 id="4.2-ssh%E9%98%B2%E7%88%86%E7%A0%B4%E6%9C%80%E4%BC%98%E9%85%8D%E7%BD%AE" tabindex="-1">4.2 SSH防爆破最优配置</h3><p>创建SSH专用配置文件：</p><pre><code class="language-bash">cat &gt; /etc/fail2ban/jail.d/sshd.conf &lt;&lt;EOF[sshd]enabled = trueport = sshfilter = sshdlogpath = /var/log/auth.logmaxretry = 3        # 3次失败即封禁bantime = 604800    # 封禁7天（60*60*24*7）findtime = 300      # 5分钟内统计失败次数ignoreip = 127.0.0.1 你的办公IP 服务器内网IPEOF</code></pre><p>重启服务使配置生效：</p><pre><code class="language-bash">systemctl restart fail2ban</code></pre><h3 id="4.3-%E5%B8%B8%E7%94%A8%E7%AE%A1%E7%90%86%E5%91%BD%E4%BB%A4" tabindex="-1">4.3 常用管理命令</h3><pre><code class="language-bash"># 查看SSH防护状态fail2ban-client status sshd# 查看所有被封禁的IPfail2ban-client banned# 解封指定IPfail2ban-client set sshd unbanip 1.2.3.4# 查看封禁日志tail -f /var/log/fail2ban.log# 统计历史攻击IP排名grep &quot;Ban&quot; /var/log/fail2ban.log | awk &#39;{print $NF}&#39; | sort | uniq -c | sort -nr | head -20# 查看今日封禁记录grep $(date +%Y-%m-%d) /var/log/fail2ban.log | grep Ban</code></pre><hr /><h2 id="%E4%BA%94%E3%80%81%E9%95%BF%E6%9C%9F%E5%AE%89%E5%85%A8%E5%8A%A0%E5%9B%BA%E5%BB%BA%E8%AE%AE" tabindex="-1">五、长期安全加固建议</h2><h3 id="5.1-%E7%B3%BB%E7%BB%9F%E7%BA%A7%E5%AE%89%E5%85%A8" tabindex="-1">5.1 系统级安全</h3><ol><li><strong>优先考虑重装系统</strong>：深度入侵后无法100%保证清除所有后门，重装是最安全的选择</li><li><strong>启用云厂商主机安全服务</strong>：如华为云企业主机安全、阿里云安骑士，提供实时入侵检测</li><li><strong>定期更新系统补丁</strong>：<code>apt update &amp;&amp; apt upgrade -y</code>，修复系统漏洞</li><li><strong>最小化安装原则</strong>：只安装必要的软件包，减少攻击面</li></ol><h3 id="5.2-%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6" tabindex="-1">5.2 访问控制</h3><ol><li><strong>强制SSH密钥认证</strong>：彻底禁用密码登录，使用RSA 4096位或Ed25519密钥</li><li><strong>IP白名单机制</strong>：所有管理端口（SSH、宝塔、数据库）仅允许指定IP访问</li><li><strong>多因素认证</strong>：为SSH和宝塔面板启用2FA双因素认证</li><li><strong>最小权限原则</strong>：每个应用使用独立用户，禁止应用以root权限运行</li></ol><h3 id="5.3-%E6%95%B0%E6%8D%AE%E5%AE%89%E5%85%A8" tabindex="-1">5.3 数据安全</h3><ol><li><strong>定期自动备份</strong>：配置每日自动备份，备份数据存储在独立的云存储中</li><li><strong>异地备份</strong>：重要数据同时备份到不同地域的存储服务</li><li><strong>备份验证</strong>：定期测试备份数据的可恢复性</li></ol><h3 id="5.4-%E7%9B%91%E6%8E%A7%E4%B8%8E%E5%91%8A%E8%AD%A6" tabindex="-1">5.4 监控与告警</h3><ol><li><strong>配置登录告警</strong>：异常IP登录时立即发送邮件/短信通知</li><li><strong>资源监控</strong>：监控CPU、内存、网络使用率，异常升高时及时告警</li><li><strong>日志审计</strong>：定期检查系统日志和应用日志，发现可疑行为</li></ol><h3 id="5.5-%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%E4%B8%93%E9%A1%B9%E5%8A%A0%E5%9B%BA" tabindex="-1">5.5 宝塔面板专项加固</h3><ol><li><strong>修改默认端口</strong>：将宝塔面板默认8888端口改为其他高端口</li><li><strong>开启面板SSL</strong>：使用HTTPS访问面板</li><li><strong>启用面板验证码</strong>：登录时需要输入验证码</li><li><strong>限制面板访问IP</strong>：仅允许你的办公IP访问面板</li><li><strong>定期更新宝塔版本</strong>：及时修复面板漏洞</li></ol><hr /><h2 id="%E5%85%AD%E3%80%81%E7%BB%93%E8%AE%BA%E4%B8%8E%E5%90%8E%E7%BB%AD%E8%A1%8C%E5%8A%A8" tabindex="-1">六、结论与后续行动</h2><p>本次攻击是典型的<strong>SSH弱密码导致的挖矿木马入侵事件</strong>，攻击者通过自动化工具爆破弱密码成功后，植入了XMRig挖矿木马，并通过crontab定时任务和SSH公钥建立了双重持久化后门。</p><h3 id="%E7%AB%8B%E5%8D%B3%E6%89%A7%E8%A1%8C%E7%9A%84%E8%A1%8C%E5%8A%A8%E9%A1%B9" tabindex="-1">立即执行的行动项</h3><ol><li>按照本指南完成所有应急响应步骤</li><li>部署Fail2Ban防爆破工具</li><li>彻底禁用SSH密码登录，仅使用密钥认证</li><li>配置云安全组IP白名单</li></ol><h3 id="7%E5%A4%A9%E5%86%85%E5%AE%8C%E6%88%90%E7%9A%84%E8%A1%8C%E5%8A%A8%E9%A1%B9" tabindex="-1">7天内完成的行动项</h3><ol><li>评估是否需要重装系统</li><li>启用云厂商主机安全服务</li><li>配置自动备份策略</li><li>完成所有应用服务的安全加固</li></ol><h3 id="%E9%95%BF%E6%9C%9F%E7%BB%B4%E6%8A%A4%E9%A1%B9" tabindex="-1">长期维护项</h3><ol><li>每月进行一次全面安全自查</li><li>每季度更新一次系统和软件</li><li>每年进行一次渗透测试</li></ol><blockquote><p>⚠️ 最后提醒：服务器安全是一个持续的过程，没有一劳永逸的解决方案。保持警惕，定期检查，及时更新，才能有效防范各类攻击。</p></blockquote>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[MySQL 分区表 + Java 定时任务 实战：基于若依架构的 API 调用日志系统优化]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/mysql-partition-job" />
                <id>tag:https://fx67ll.xyz,2026-03-02:mysql-partition-job</id>
                <published>2026-03-02T14:46:10+08:00</published>
                <updated>2026-03-02T15:11:02+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>在AI服务调用日益频繁的业务场景中，<code>fx67ll_ai_request_log</code>作为记录每一次AI请求的明细日志表，会随着时间推移积累海量数据。日调用量达到十万级别时，半年后单表数据量轻松突破千万甚至上亿。此时，针对时间范围的查询（如统计某个月的token消耗）会因全表扫描变得极其缓慢，严重影响前端报表的响应速度，甚至拖累主业务。</p><p>为了解决这一问题，我们引入两大优化方案：</p><ol><li><strong>按<code>request_time</code>字段进行月份分区</strong>：将数据物理拆分到不同分区，查询时仅扫描对应分区。</li><li><strong>定时任务生成统计数据</strong>：将海量明细数据的实时统计转换为离线预计算，结果存入日/月/年统计表，前端查询毫秒级响应。</li></ol><p>本文结合若依（SpringBoot+MySQL）架构，详细讲解这两个方案的具体实现逻辑、操作步骤以及生产环境落地的自动化保障措施。</p><hr /><h2 id="%E4%B8%80%E3%80%81%E4%BC%98%E5%8C%961%EF%BC%9Afx67ll_ai_request_log-%E6%8C%89-request_time-%E6%9C%88%E4%BB%BD%E5%88%86%E5%8C%BA" tabindex="-1">一、优化1：fx67ll_ai_request_log 按 request_time 月份分区</h2><h3 id="1.1-%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%88%E4%BB%BD%E5%88%86%E5%8C%BA%E8%A1%A8%EF%BC%9F" tabindex="-1">1.1 什么是月份分区表？</h3><p>MySQL分区表在逻辑上仍是一张表，但物理存储上根据指定的规则（如<code>RANGE</code>、<code>LIST</code>、<code>HASH</code>）将数据分散到多个物理文件中。我们采用<strong>RANGE分区</strong>，以<code>request_time</code>的日期值作为分区依据，每个月的数据独立存放。查询时，优化器会自动过滤掉无关分区，只扫描必要分区，极大提升查询效率。</p><h3 id="1.2-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%81%9A%E8%BF%99%E4%B8%AA%E4%BC%98%E5%8C%96%EF%BC%9F" tabindex="-1">1.2 为什么要做这个优化？</h3><ul><li><strong>查询性能提升</strong>：统计某月的token用量时，普通表需扫描全表（可能数千万行），而分区表仅扫描对应月份分区（约几十万行），效率提升10倍以上。</li><li><strong>维护便捷</strong>：删除半年以上的历史数据，只需<code>DROP PARTITION</code>，瞬间释放磁盘空间，远快于<code>DELETE</code>操作。</li><li><strong>备份灵活</strong>：可针对单个分区进行备份，降低运维成本。</li></ul><h3 id="1.3-%E5%88%86%E5%8C%BA%E8%A1%A8%E5%88%9B%E5%BB%BA%E6%AD%A5%E9%AA%A4%EF%BC%88%E5%9F%BA%E4%BA%8E%E5%8E%9F%E8%A1%A8%E7%BB%93%E6%9E%84%EF%BC%89" tabindex="-1">1.3 分区表创建步骤（基于原表结构）</h3><h4 id="%EF%BC%881%EF%BC%89%E8%B0%83%E6%95%B4%E4%B8%BB%E9%94%AE%EF%BC%8C%E5%8C%85%E5%90%AB%E5%88%86%E5%8C%BA%E5%AD%97%E6%AE%B5" tabindex="-1">（1）调整主键，包含分区字段</h4><p>MySQL要求<strong>分区字段必须包含在主键或唯一索引中</strong>。原表主键为<code>log_id</code>，需改为联合主键<code>(log_id, request_time)</code>。</p><h4 id="%EF%BC%882%EF%BC%89%E5%88%9B%E5%BB%BA%E5%88%86%E5%8C%BA%E8%A1%A8%EF%BC%88%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%9B%E5%BB%BA%E8%AF%AD%E5%8F%A5%EF%BC%89" tabindex="-1">（2）创建分区表（替换原创建语句）</h4><pre><code class="language-sql">CREATE TABLE &#96;fx67ll_ai_request_log&#96; (  &#96;log_id&#96; bigint(20) NOT NULL AUTO_INCREMENT COMMENT &#39;日志ID（主键）&#39;,  &#96;prompt_id&#96; bigint(20) DEFAULT NULL COMMENT &#39;使用的模板ID&#39;,  &#96;scene_id&#96; bigint(20) DEFAULT NULL COMMENT &#39;关联场景ID&#39;,  &#96;model_id&#96; bigint(20) NOT NULL COMMENT &#39;调用的模型ID&#39;,  &#96;model_vendor&#96; varchar(30) NOT NULL COMMENT &#39;模型厂商&#39;,  &#96;request_content&#96; text DEFAULT &#39;&#39; COMMENT &#39;请求内容&#39;,  &#96;response_content&#96; text DEFAULT &#39;&#39; COMMENT &#39;响应内容&#39;,  &#96;prompt_tokens&#96; int(11) DEFAULT 0 COMMENT &#39;输入token数&#39;,  &#96;completion_tokens&#96; int(11) DEFAULT 0 COMMENT &#39;输出token数&#39;,  &#96;total_tokens&#96; int(11) DEFAULT 0 COMMENT &#39;总token数&#39;,  &#96;cost&#96; decimal(10,6) DEFAULT 0.000000 COMMENT &#39;预估费用&#39;,  &#96;duration_ms&#96; int(11) DEFAULT 0 COMMENT &#39;请求耗时&#39;,  &#96;http_status&#96; int(3) DEFAULT NULL COMMENT &#39;HTTP状态码&#39;,  &#96;call_status&#96; char(2) DEFAULT &#39;00&#39; COMMENT &#39;调用状态（00:成功 01:失败 02:限流 03:熔断）&#39;,  &#96;error_msg&#96; text DEFAULT &#39;&#39; COMMENT &#39;错误信息&#39;,  &#96;caller_ip&#96; varchar(32) DEFAULT &#39;&#39; COMMENT &#39;调用者IP&#39;,  &#96;request_time&#96; datetime DEFAULT CURRENT_TIMESTAMP COMMENT &#39;请求时间&#39;,  &#96;create_by&#96; varchar(64) DEFAULT &#39;&#39; COMMENT &#39;调用者标识&#39;,  PRIMARY KEY (&#96;log_id&#96;, &#96;request_time&#96;)   -- 分区字段必须包含在主键中) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 PARTITION BY RANGE (TO_DAYS(request_time)) (   PARTITION p202602 VALUES LESS THAN (TO_DAYS(&#39;2026-03-01&#39;)), -- 2026年2月  PARTITION p202603 VALUES LESS THAN (TO_DAYS(&#39;2026-04-01&#39;)), -- 2026年3月  PARTITION p202604 VALUES LESS THAN (TO_DAYS(&#39;2026-05-01&#39;)), -- 2026年4月  PARTITION p_default VALUES LESS THAN MAXVALUE               -- 默认分区（防止数据落入异常）) COMMENT=&#39;AI调用请求日志表（按request_time月份分区）&#39;;-- 保留原有索引（分区表自动适配）ALTER TABLE &#96;fx67ll_ai_request_log&#96; ADD KEY &#96;idx_request_time&#96; (&#96;request_time&#96;),ADD KEY &#96;idx_prompt_id&#96; (&#96;prompt_id&#96;),ADD KEY &#96;idx_scene_id&#96; (&#96;scene_id&#96;),ADD KEY &#96;idx_model_id&#96; (&#96;model_id&#96;),ADD KEY &#96;idx_create_by&#96; (&#96;create_by&#96;),ADD KEY &#96;idx_call_status&#96; (&#96;call_status&#96;),ADD KEY &#96;idx_request_time_vendor&#96; (&#96;request_time&#96;, &#96;model_vendor&#96;),ADD KEY &#96;idx_scene_model_time&#96; (&#96;scene_id&#96;, &#96;model_id&#96;, &#96;request_time&#96;),ADD KEY &#96;idx_create_by_time&#96; (&#96;create_by&#96;, &#96;request_time&#96;);</code></pre><h4 id="%EF%BC%883%EF%BC%89%E5%88%86%E5%8C%BA%E7%BB%B4%E6%8A%A4%EF%BC%88%E6%89%8B%E5%8A%A8%E7%A4%BA%E4%BE%8B%EF%BC%89" tabindex="-1">（3）分区维护（手动示例）</h4><ul><li><p><strong>新增分区</strong>（月底前创建下月分区）：</p><pre><code class="language-sql">-- 删除默认分区（临时）ALTER TABLE fx67ll_ai_request_log DROP PARTITION p_default;-- 创建2026年5月分区ALTER TABLE fx67ll_ai_request_log ADD PARTITION (  PARTITION p202605 VALUES LESS THAN (TO_DAYS(&#39;2026-06-01&#39;)));-- 重建默认分区ALTER TABLE fx67ll_ai_request_log ADD PARTITION (  PARTITION p_default VALUES LESS THAN MAXVALUE);</code></pre></li><li><p><strong>删除历史分区</strong>（如删除6个月前数据）：</p><pre><code class="language-sql">ALTER TABLE fx67ll_ai_request_log DROP PARTITION p202602;</code></pre></li></ul><h4 id="%EF%BC%884%EF%BC%89%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8A%BF%E7%A4%BA%E4%BE%8B" tabindex="-1">（4）查询优势示例</h4><pre><code class="language-sql">-- 统计2026年3月某模型的token消耗SELECT model_id, SUM(total_tokens) AS total FROM fx67ll_ai_request_log WHERE request_time BETWEEN &#39;2026-03-01 00:00:00&#39; AND &#39;2026-03-31 23:59:59&#39;  AND model_id = 1001;-- 执行计划会显示只扫描p202603分区</code></pre><h3 id="1.4-%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1">1.4 注意事项</h3><ul><li>分区字段<code>request_time</code>必须包含在主键中（MySQL限制）。</li><li>跨分区查询（如2月至4月）性能仍优于全表扫描，但尽量缩小时间范围。</li><li>若使用MyBatis/MyBatis-Plus，无需修改代码，SQL完全兼容。</li></ul><hr /><h2 id="%E4%BA%8C%E3%80%81%E5%88%86%E5%8C%BA%E8%87%AA%E5%8A%A8%E5%8C%96%E5%88%9B%E5%BB%BA%EF%BC%9A%E5%91%8A%E5%88%AB%E6%89%8B%E5%8A%A8%E7%BB%B4%E6%8A%A4" tabindex="-1">二、分区自动化创建：告别手动维护</h2><h3 id="2.1-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E8%87%AA%E5%8A%A8%E5%8C%96%EF%BC%9F" tabindex="-1">2.1 为什么需要自动化？</h3><p>手动每月创建分区存在巨大风险：</p><ul><li>可能遗忘创建，导致新数据落入<code>p_default</code>默认分区，后续查询无法享受分区剪枝优势；</li><li>操作失误（如分区名写错、临界值算错）可能导致数据存储异常；</li><li>增加运维负担，无法应对快速发展的业务。</li></ul><p>生产环境必须实现分区自动创建，核心原则：</p><ul><li><strong>提前创建</strong>：每月提前5-7天创建下月分区（如25号），留足缓冲；</li><li><strong>幂等性</strong>：创建前检查分区是否已存在，避免重复执行报错；</li><li><strong>可监控</strong>：失败时及时告警。</li></ul><h3 id="2.2-%E6%96%B9%E6%A1%88%E4%B8%80%EF%BC%9Aspringboot%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%EF%BC%88%E6%8E%A8%E8%8D%90%EF%BC%8C%E8%B4%B4%E5%90%88%E8%8B%A5%E4%BE%9D%E6%9E%B6%E6%9E%84%EF%BC%89" tabindex="-1">2.2 方案一：SpringBoot定时任务（推荐，贴合若依架构）</h3><p>利用若依框架自带的<code>@Scheduled</code>定时任务能力，将分区创建与业务统计任务整合，统一监控。</p><h4 id="%EF%BC%881%EF%BC%89mapper%E6%8E%A5%E5%8F%A3%E6%89%A9%E5%B1%95" tabindex="-1">（1）Mapper接口扩展</h4><p>在<code>AiRequestLogMapper</code>中添加检查分区是否存在和执行DDL的方法：</p><pre><code class="language-java">public interface AiRequestLogMapper {    // ... 其他方法    /**     * 检查分区是否已存在     * @param tableName 表名     * @param partitionName 分区名（如p202603）     * @return 存在返回1，不存在返回0     */    @Select(&quot;SELECT COUNT(*) FROM information_schema.PARTITIONS &quot; +            &quot;WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = #{tableName} AND PARTITION_NAME = #{partitionName}&quot;)    int checkPartitionExists(@Param(&quot;tableName&quot;) String tableName, @Param(&quot;partitionName&quot;) String partitionName);    /**     * 执行分区创建的原生SQL（动态SQL）     * @param sql 要执行的DDL语句     */    @Update({&quot;${sql}&quot;})    void executePartitionSql(@Param(&quot;sql&quot;) String sql);}</code></pre><h4 id="%EF%BC%882%EF%BC%89%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E7%B1%BB%EF%BC%9A%E8%87%AA%E5%8A%A8%E5%88%9B%E5%BB%BA%E4%B8%8B%E6%9C%88%E5%88%86%E5%8C%BA" tabindex="-1">（2）定时任务类：自动创建下月分区</h4><p>在原有的统计任务类中新增方法（复用若依的定时任务配置，<code>@EnableScheduling</code>已默认开启）：</p><pre><code class="language-java">import lombok.extern.slf4j.Slf4j;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import javax.annotation.Resource;import java.time.LocalDate;import java.time.format.DateTimeFormatter;@Slf4j@Componentpublic class AiRequestStatTask {    @Resource    private AiRequestLogMapper aiRequestLogMapper;    private static final String TABLE_NAME = &quot;fx67ll_ai_request_log&quot;;    /**     * 每月25号凌晨3点执行：自动创建下月的分区     * cron = &quot;0 0 3 25 * ?&quot;     */    @Scheduled(cron = &quot;0 0 3 25 * ?&quot;)    public void autoCreateNextMonthPartition() {        log.info(&quot;开始执行AI日志表自动创建下月分区任务&quot;);        try {            LocalDate now = LocalDate.now();            LocalDate nextMonth = now.plusMonths(1);       // 下月            LocalDate nextNextMonth = nextMonth.plusMonths(1); // 下下月（临界值）            String partitionName = &quot;p&quot; + nextMonth.format(DateTimeFormatter.ofPattern(&quot;yyyyMM&quot;));            String partitionLimitDate = nextNextMonth.format(DateTimeFormatter.ISO_DATE); // yyyy-MM-dd            String partitionLimitSql = &quot;TO_DAYS(&#39;&quot; + partitionLimitDate + &quot;&#39;)&quot;;            // 检查分区是否已存在            int exists = aiRequestLogMapper.checkPartitionExists(TABLE_NAME, partitionName);            if (exists &gt; 0) {                log.info(&quot;下月分区{}已存在，无需重复创建&quot;, partitionName);                return;            }            // 执行分区创建：删默认分区 → 建下月分区 → 重建默认分区            aiRequestLogMapper.executePartitionSql(&quot;ALTER TABLE &quot; + TABLE_NAME + &quot; DROP PARTITION p_default&quot;);            String createSql = String.format(                &quot;ALTER TABLE %s ADD PARTITION (PARTITION %s VALUES LESS THAN (%s))&quot;,                TABLE_NAME, partitionName, partitionLimitSql            );            aiRequestLogMapper.executePartitionSql(createSql);            aiRequestLogMapper.executePartitionSql(&quot;ALTER TABLE &quot; + TABLE_NAME + &quot; ADD PARTITION (PARTITION p_default VALUES LESS THAN MAXVALUE)&quot;);            log.info(&quot;AI日志表下月分区{}创建成功，临界值：{}&quot;, partitionName, partitionLimitDate);        } catch (Exception e) {            log.error(&quot;AI日志表自动创建分区任务执行失败&quot;, e);            // 调用告警服务（若依可集成钉钉/企业微信）            // alarmService.sendAlarm(&quot;AI日志表分区创建失败&quot;, e.getMessage());        }    }}</code></pre><h4 id="%EF%BC%883%EF%BC%89%E6%8B%93%E5%B1%95%EF%BC%9A%E8%87%AA%E5%8A%A8%E5%88%A0%E9%99%A4%E5%8E%86%E5%8F%B2%E5%88%86%E5%8C%BA%EF%BC%88%E4%BF%9D%E7%95%996%E4%B8%AA%E6%9C%88%E6%95%B0%E6%8D%AE%EF%BC%89" tabindex="-1">（3）拓展：自动删除历史分区（保留6个月数据）</h4><p>每月1号凌晨执行，删除6个月前的分区：</p><pre><code class="language-java">/** * 每月1号凌晨4点执行：删除6个月前的历史分区（保留6个月日志） * cron = &quot;0 0 4 1 * ?&quot; */@Scheduled(cron = &quot;0 0 4 1 * ?&quot;)public void autoDeleteHistoryPartition() {    log.info(&quot;开始执行AI日志表自动删除历史分区任务&quot;);    try {        LocalDate delMonth = LocalDate.now().minusMonths(6);        String partitionName = &quot;p&quot; + delMonth.format(DateTimeFormatter.ofPattern(&quot;yyyyMM&quot;));        int exists = aiRequestLogMapper.checkPartitionExists(TABLE_NAME, partitionName);        if (exists &gt; 0) {            aiRequestLogMapper.executePartitionSql(&quot;ALTER TABLE &quot; + TABLE_NAME + &quot; DROP PARTITION &quot; + partitionName);            log.info(&quot;历史分区{}删除成功&quot;, partitionName);        } else {            log.info(&quot;历史分区{}不存在，无需删除&quot;, partitionName);        }    } catch (Exception e) {        log.error(&quot;AI日志表自动删除历史分区任务失败&quot;, e);        // alarmService.sendAlarm(&quot;AI日志表分区删除失败&quot;, e.getMessage());    }}</code></pre><h3 id="2.3-%E6%96%B9%E6%A1%88%E4%BA%8C%EF%BC%9Amysql%E4%BA%8B%E4%BB%B6%E8%B0%83%E5%BA%A6%E5%99%A8%EF%BC%88%E7%BA%AF%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0%EF%BC%89" tabindex="-1">2.3 方案二：MySQL事件调度器（纯数据库实现）</h3><p>适合无代码侵入、DBA主导的场景。需开启MySQL事件调度器。</p><h4 id="%EF%BC%881%EF%BC%89%E5%88%9B%E5%BB%BA%E5%AD%98%E5%82%A8%E8%BF%87%E7%A8%8B" tabindex="-1">（1）创建存储过程</h4><pre><code class="language-sql">DELIMITER //CREATE PROCEDURE auto_create_ai_log_partition()BEGIN    DECLARE table_name VARCHAR(50) DEFAULT &#39;fx67ll_ai_request_log&#39;;    DECLARE next_month VARCHAR(6);    DECLARE next_next_month_date VARCHAR(10);    DECLARE partition_name VARCHAR(20);    DECLARE partition_count INT DEFAULT 0;    SET next_month = DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), &#39;%Y%m&#39;);    SET next_next_month_date = DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 2 MONTH), &#39;%Y-%m-01&#39;);    SET partition_name = CONCAT(&#39;p&#39;, next_month);    SELECT COUNT(*) INTO partition_count    FROM information_schema.PARTITIONS    WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = table_name AND PARTITION_NAME = partition_name;    IF partition_count = 0 THEN        SET @sql = CONCAT(&#39;ALTER TABLE &#39;, table_name, &#39; DROP PARTITION p_default&#39;);        PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;        SET @sql = CONCAT(&#39;ALTER TABLE &#39;, table_name, &#39; ADD PARTITION (PARTITION &#39;, partition_name,                           &#39; VALUES LESS THAN (TO_DAYS(\&#39;&#39;, next_next_month_date, &#39;\&#39;)))&#39;);        PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;        SET @sql = CONCAT(&#39;ALTER TABLE &#39;, table_name, &#39; ADD PARTITION (PARTITION p_default VALUES LESS THAN MAXVALUE)&#39;);        PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;        -- 可写入操作日志表        INSERT INTO sys_oper_log (title, business_type, oper_type, oper_content, create_time)        VALUES (&#39;AI日志表分区自动创建&#39;, 1, 1, CONCAT(&#39;创建分区：&#39;, partition_name), NOW());    END IF;END //DELIMITER ;</code></pre><h4 id="%EF%BC%882%EF%BC%89%E5%88%9B%E5%BB%BA%E5%AE%9A%E6%97%B6%E4%BA%8B%E4%BB%B6%EF%BC%88%E6%AF%8F%E6%9C%8825%E5%8F%B73%E7%82%B9%E6%89%A7%E8%A1%8C%EF%BC%89" tabindex="-1">（2）创建定时事件（每月25号3点执行）</h4><pre><code class="language-sql">CREATE EVENT IF NOT EXISTS event_ai_log_auto_create_partitionON SCHEDULE EVERY 1 MONTHSTARTS DATE_ADD(DATE_FORMAT(CURDATE(), &#39;%Y-%m-25&#39;), INTERVAL 3 HOUR)DOCALL auto_create_ai_log_partition();ALTER EVENT event_ai_log_auto_create_partition ENABLE;</code></pre><h4 id="%EF%BC%883%EF%BC%89%E7%BB%B4%E6%8A%A4%E5%91%BD%E4%BB%A4" tabindex="-1">（3）维护命令</h4><pre><code class="language-sql">SHOW EVENTS;                         -- 查看事件ALTER EVENT event_xxx DISABLE;       -- 关闭事件DROP EVENT event_xxx;                -- 删除事件</code></pre><h3 id="2.4-%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94%E4%B8%8E%E9%80%89%E5%9E%8B%E5%BB%BA%E8%AE%AE" tabindex="-1">2.4 方案对比与选型建议</h3><table><thead><tr><th>特性</th><th>方案1：SpringBoot定时任务</th><th>方案2：MySQL事件调度器</th></tr></thead><tbody><tr><td>技术栈贴合度</td><td>极高（复用若依体系）</td><td>低（纯数据库，与代码解耦）</td></tr><tr><td>维护成本</td><td>低（开发统一维护）</td><td>中（需DBA维护MySQL事件）</td></tr><tr><td>监控能力</td><td>强（若依定时任务监控+告警）</td><td>弱（需额外监控事件执行状态）</td></tr><tr><td>侵入性</td><td>低（仅新增Mapper+方法）</td><td>无（纯数据库）</td></tr><tr><td>跨库适配性</td><td>强（支持多数据源）</td><td>弱（单库生效）</td></tr></tbody></table><p><strong>推荐采用方案1</strong>：完全贴合若依架构，可与现有的统计任务整合，利用若依的定时任务监控页面查看执行状态、失败告警，且便于扩展多数据源场景。</p><h3 id="2.5-%E7%94%9F%E4%BA%A7%E5%85%9C%E5%BA%95%E6%8E%AA%E6%96%BD" tabindex="-1">2.5 生产兜底措施</h3><ul><li><strong>默认分区保底</strong>：始终保留<code>p_default</code>分区，即使自动创建任务失败，新数据也能正常插入（后续人工处理后可迁移数据）。</li><li><strong>分区状态监控</strong>：在若依监控中增加数据库分区检查，若发现下月分区未创建，及时告警。</li><li><strong>手动重试机制</strong>：若依的定时任务支持手动执行，分区创建失败后可人工触发。</li></ul><hr /><h2 id="%E4%B8%89%E3%80%81%E4%BC%98%E5%8C%962%EF%BC%9A%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E7%94%9F%E6%88%90%E7%BB%9F%E8%AE%A1%E6%95%B0%E6%8D%AE%EF%BC%88%E6%97%A5%2F%E6%9C%88%2F%E5%B9%B4%EF%BC%89" tabindex="-1">三、优化2：定时任务生成统计数据（日/月/年）</h2><h3 id="3.1-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E5%AE%9A%E6%97%B6%E7%BB%9F%E8%AE%A1%EF%BC%9F" tabindex="-1">3.1 为什么需要定时统计？</h3><p>直接对亿级明细日志表执行<code>SUM</code>、<code>COUNT</code>等聚合查询，会导致：</p><ul><li>响应缓慢（数秒至数十秒），前端报表用户体验差；</li><li>高并发下大量计算占用数据库IO和CPU，影响AI调用的主业务。</li></ul><p>解决方案：通过定时任务（每日凌晨）预统计前一天的数据，将结果存入轻量级的日统计表；再基于日统计表生成月/年统计，前端查询直接读取统计表，实现毫秒级响应。</p><h3 id="3.2-%E7%BB%9F%E8%AE%A1%E8%A1%A8%E8%AE%BE%E8%AE%A1" tabindex="-1">3.2 统计表设计</h3><ul><li><strong>日统计表</strong> <code>fx67ll_ai_request_daily_log</code>：按<code>daily_log_date</code>（日期）、<code>model_id</code>、<code>scene_id</code>聚合，存储调用次数、token数、费用等。</li><li><strong>月统计表</strong> <code>fx67ll_ai_request_monthly_log</code>：按月聚合，数据来自日统计表。</li><li><strong>年统计表</strong> <code>fx67ll_ai_request_yearly_log</code>：按年聚合，数据来自月统计表。</li></ul><p>（表结构略，可根据实际需求设计）</p><h3 id="3.3-%E6%97%A5%E7%BB%9F%E8%AE%A1%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E5%AE%9E%E7%8E%B0" tabindex="-1">3.3 日统计定时任务实现</h3><h4 id="%EF%BC%881%EF%BC%89%E8%8B%A5%E4%BE%9D%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E9%85%8D%E7%BD%AE%EF%BC%88%E5%B7%B2%E9%BB%98%E8%AE%A4%E5%BC%80%E5%90%AF%EF%BC%89" tabindex="-1">（1）若依定时任务配置（已默认开启）</h4><pre><code class="language-java">@Configuration@EnableSchedulingpublic class ScheduleConfig {}</code></pre><h4 id="%EF%BC%882%EF%BC%89%E6%97%A5%E7%BB%9F%E8%AE%A1%E4%BB%BB%E5%8A%A1%E4%BB%A3%E7%A0%81" tabindex="-1">（2）日统计任务代码</h4><pre><code class="language-java">@Component@Slf4jpublic class AiRequestStatTask {    @Resource    private AiRequestLogMapper aiRequestLogMapper;    @Resource    private AiRequestDailyLogMapper aiRequestDailyLogMapper;    /**     * 每日凌晨1点执行：统计前一天的AI调用数据     * cron = &quot;0 0 1 * * ?&quot;     */    @Scheduled(cron = &quot;0 0 1 * * ?&quot;)    @Transactional(rollbackFor = Exception.class)    public void statDailyData() {        log.info(&quot;开始执行日统计任务&quot;);        try {            LocalDate yesterday = LocalDate.now().minusDays(1);            String yesterdayStr = yesterday.format(DateTimeFormatter.ISO_DATE);            String startTime = yesterdayStr + &quot; 00:00:00&quot;;            String endTime = yesterdayStr + &quot; 23:59:59&quot;;            // 幂等性：删除该日期已有的统计数据（避免重复执行导致数据翻倍）            aiRequestDailyLogMapper.deleteByDate(yesterdayStr);            // 从明细表汇总            List&lt;DailyStatDTO&gt; statList = aiRequestLogMapper.statDailyData(startTime, endTime);            if (statList != null &amp;&amp; !statList.isEmpty()) {                for (DailyStatDTO dto : statList) {                    AiRequestDailyLog dailyLog = new AiRequestDailyLog();                    dailyLog.setDailyLogDate(yesterday);                    dailyLog.setModelId(dto.getModelId());                    dailyLog.setSceneId(dto.getSceneId());                    dailyLog.setTotalRequests(dto.getTotalRequests());                    dailyLog.setFailRequests(dto.getFailRequests());                    dailyLog.setLimitRequests(dto.getLimitRequests());                    dailyLog.setCircuitRequests(dto.getCircuitRequests());                    dailyLog.setTotalPromptTokens(dto.getTotalPromptTokens());                    dailyLog.setTotalCompletionTokens(dto.getTotalCompletionTokens());                    dailyLog.setTotalCost(dto.getTotalCost());                    dailyLog.setAvgDurationMs(dto.getTotalDurationMs() / dto.getTotalRequests());                    aiRequestDailyLogMapper.insert(dailyLog);                }            }            log.info(&quot;日统计任务执行完成，日期：{}&quot;, yesterdayStr);        } catch (Exception e) {            log.error(&quot;日统计任务执行失败&quot;, e);            // 告警通知        }    }}</code></pre><h4 id="%EF%BC%883%EF%BC%89mybatis%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6%EF%BC%88%E7%BB%9F%E8%AE%A1sql%EF%BC%89" tabindex="-1">（3）MyBatis映射文件（统计SQL）</h4><pre><code class="language-xml">&lt;!-- AiRequestLogMapper.xml --&gt;&lt;select id=&quot;statDailyData&quot; resultType=&quot;com.xxx.dto.DailyStatDTO&quot;&gt;    SELECT         model_id,        scene_id,        COUNT(*) AS total_requests,        SUM(CASE WHEN call_status = &#39;01&#39; THEN 1 ELSE 0 END) AS fail_requests,        SUM(CASE WHEN call_status = &#39;02&#39; THEN 1 ELSE 0 END) AS limit_requests,        SUM(CASE WHEN call_status = &#39;03&#39; THEN 1 ELSE 0 END) AS circuit_requests,        SUM(prompt_tokens) AS total_prompt_tokens,        SUM(completion_tokens) AS total_completion_tokens,        SUM(cost) AS total_cost,        SUM(duration_ms) AS total_duration_ms    FROM fx67ll_ai_request_log    WHERE request_time BETWEEN #{startTime} AND #{endTime}    GROUP BY model_id, scene_id&lt;/select&gt;</code></pre><pre><code class="language-xml">&lt;!-- AiRequestDailyLogMapper.xml --&gt;&lt;delete id=&quot;deleteByDate&quot;&gt;    DELETE FROM fx67ll_ai_request_daily_log    WHERE daily_log_date = #{date}&lt;/delete&gt;</code></pre><h3 id="3.4-%E6%9C%88%2F%E5%B9%B4%E7%BB%9F%E8%AE%A1%E4%BB%BB%E5%8A%A1" tabindex="-1">3.4 月/年统计任务</h3><p>月统计任务在每月1日凌晨2点执行，汇总上月的日统计数据；年统计在每年1月1日凌晨执行。代码结构与日统计类似，只需修改时间范围和来源表。</p><p><strong>月统计核心SQL示例</strong>（从日统计表聚合）：</p><pre><code class="language-sql">SELECT     DATE_FORMAT(daily_log_date, &#39;%Y-%m&#39;) AS stat_month,    model_id,    scene_id,    SUM(total_requests) AS total_requests,    SUM(total_prompt_tokens) AS total_prompt_tokens,    SUM(total_cost) AS total_costFROM fx67ll_ai_request_daily_logWHERE daily_log_date BETWEEN #{startDate} AND #{endDate}GROUP BY stat_month, model_id, scene_id</code></pre><h3 id="3.5-%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1">3.5 注意事项</h3><ul><li><strong>幂等性</strong>：统计前先删除目标日期/月份的数据，保证任务可重复执行而不产生重复记录。</li><li><strong>事务</strong>：统计方法添加<code>@Transactional</code>，确保写入一致性。</li><li><strong>执行时间</strong>：选择业务低峰期（凌晨），避免影响主业务。</li><li><strong>异常监控</strong>：任务内捕获异常，记录日志并触发告警（若依支持集成钉钉、邮件等）。</li></ul><hr /><h2 id="%E5%9B%9B%E3%80%81%E6%80%BB%E7%BB%93%E4%B8%8E%E8%90%BD%E5%9C%B0%E5%BB%BA%E8%AE%AE" tabindex="-1">四、总结与落地建议</h2><h3 id="4.1-%E4%B8%A4%E5%A4%A7%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88%E7%9A%84%E6%A0%B8%E5%BF%83%E4%BB%B7%E5%80%BC" tabindex="-1">4.1 两大优化方案的核心价值</h3><ul><li><strong>月份分区表</strong>：从根本上解决了海量明细日志的查询性能问题，将查询范围从全表缩小到单月分区；同时简化了历史数据清理操作。</li><li><strong>定时任务统计数据</strong>：将实时聚合转化为离线预计算，避免高频聚合查询拖垮数据库，前端报表响应速度从秒级降至毫秒级。</li></ul><h3 id="4.2-%E8%90%BD%E5%9C%B0%E4%BC%98%E5%85%88%E7%BA%A7%E5%BB%BA%E8%AE%AE" tabindex="-1">4.2 落地优先级建议</h3><ol><li><strong>首先实施分区表</strong>：创建分区表并迁移历史数据（可使用<code>ALTER TABLE ... REORGANIZE PARTITION</code>或<code>pt-online-schema-change</code>工具），同时实现分区自动化创建。</li><li><strong>然后实现日统计任务</strong>：先建立日统计表，每日凌晨统计前一日数据，支撑近期的报表需求。</li><li><strong>最后扩展月/年统计</strong>：基于日统计表聚合生成月/年统计，满足长期成本核算和趋势分析。</li></ol><h3 id="4.3-%E6%95%B4%E4%BD%93%E6%94%B6%E7%9B%8A" tabindex="-1">4.3 整体收益</h3><ul><li>查询性能提升10倍以上，用户体验大幅改善。</li><li>数据库负载降低，主业务稳定性得到保障。</li><li>运维自动化，减少人工操作风险，释放开发/运维精力。</li></ul><p>通过以上两个优化，你的AI日志系统将具备处理亿级数据的能力，为业务快速增长打下坚实基础。</p><hr /><p><strong>附：若依框架定时任务监控</strong><br />在若依管理后台的“系统监控”→“定时任务”中，可查看所有<code>@Scheduled</code>任务的执行记录，支持手动执行一次、暂停、恢复等操作，极大方便了生产维护。</p><p>希望本文能帮助你顺利落地MySQL分区表与定时统计任务，如有疑问或需要进一步探讨，欢迎交流！</p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2026/2026-02/mysql-partition-job" target="_blank">本文 github 仓库地址</a>，为我点一颗 Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[简述日常开发中的 Java Job]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/java-job" />
                <id>tag:https://fx67ll.xyz,2026-02-13:java-job</id>
                <published>2026-02-13T16:42:52+08:00</published>
                <updated>2026-02-13T17:19:32+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E5%89%8D%E8%A8%80" tabindex="-1">前言</h3><p>在 Java 后端开发的日常工作中，我们经常会遇到一些<strong>非实时、需要后台自动执行</strong>的业务需求：比如每天凌晨自动统计昨日的订单数据生成报表，用户支付成功后异步发送提醒短信，或者在低峰期批量同步不同系统间的数据。这些需求的实现，都离不开 Java 中的「Job（任务 / 作业）」机制。</p><p>不同于接口这类需要实时响应用户请求的业务逻辑，Job 更偏向于后台的 “自动化工作者”，它可以在指定的时间、特定的条件下自动执行，也可以由某个业务事件触发执行。合理使用 Job 机制，不仅能提升系统的响应速度，还能避免大量同步操作对系统性能造成的冲击。但如果 Job 的设计和实现不合理，也可能会引发 CPU 占用过高、数据库连接耗尽、数据重复处理等性能问题，甚至拖垮整个应用系统。</p><p>本文将从 Java Job 的基础定义出发，为大家详细介绍开发 Job 常用的工具与技术栈、核心的触发方式，以及在实际开发中如何避免 Job 带来的性能问题，帮助大家更高效、更稳定地实现各类后台任务需求。</p><hr /><h3 id="%E4%B8%80%E3%80%81java-%E4%B8%AD%E3%80%8Cjob%E3%80%8D%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F" tabindex="-1">一、Java 中「Job」是什么？</h3><p>Java 里的<strong>Job（任务 / 作业）</strong> 本质是一段需要在<strong>特定时间、特定条件下后台执行</strong> 的业务逻辑，区别于接口这类实时响应的请求，属于「非实时、后台运行」的任务。<br />常见场景：</p><ul><li><p>定时任务：每天凌晨 2 点统计昨日订单数据、每月 1 号生成账单；</p></li><li><p>异步任务：订单支付成功后异步发送短信 / 邮件、异步处理大文件解析；</p></li><li><p>批处理任务：批量同步数据、批量更新数据库数据。</p></li></ul><hr /><h3 id="%E4%BA%8C%E3%80%81java-%E4%B8%AD%E5%BC%80%E5%8F%91-job-%E7%9A%84%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7-%2F-%E6%A1%86%E6%9E%B6" tabindex="-1">二、Java 中开发 Job 的常用工具 / 框架</h3><p>不同复杂度的场景对应不同工具，从简单到复杂分为以下几类：</p><table><thead><tr><th>工具 / 框架</th><th>开发语言</th><th>适用场景</th><th>核心特点</th></tr></thead><tbody><tr><td>JDK 原生</td><td>Java</td><td>极简场景（单线程、无分布式）</td><td>1. Timer/TimerTask（单线程，缺陷多）；2. ScheduledExecutorService（并发包，多线程，替代 Timer）</td></tr><tr><td>Spring Task</td><td>Java + Spring</td><td>单体应用、简单定时任务</td><td>轻量、无额外依赖，集成 Spring，支持 CRON 表达式</td></tr><tr><td>Quartz</td><td>Java</td><td>复杂定时任务、分布式基础场景</td><td>功能全面（CRON、任务持久化、集群），需手动集成 Spring</td></tr><tr><td>XXL-Job</td><td>Java + Spring</td><td>分布式集群、企业级任务调度</td><td>开箱即用（自带管理控制台）、支持分片、失败重试、日志追踪</td></tr><tr><td>Elastic-Job</td><td>Java + Spring</td><td>分布式高可用任务</td><td>阿里开源，支持分片、弹性扩容、分布式锁</td></tr><tr><td>Apache Airflow</td><td>Python/Java</td><td>复杂批处理、任务依赖编排</td><td>侧重大数据场景的任务流调度</td></tr></tbody></table><h4 id="%E6%A0%B8%E5%BF%83%E7%A4%BA%E4%BE%8B%EF%BC%88%E6%96%B0%E6%89%8B%E5%8F%8B%E5%A5%BD%EF%BC%89" tabindex="-1">核心示例（新手友好）</h4><h5 id="1.-spring-task%EF%BC%88%E6%9C%80%E5%B8%B8%E7%94%A8%EF%BC%8C%E8%BD%BB%E9%87%8F%EF%BC%89" tabindex="-1">1. Spring Task（最常用，轻量）</h5><p>无需额外引入依赖（Spring Context 自带），直接通过注解即可实现定时 Job。在使用 CRON 表达式时，需要注意表达式的格式：<code>秒 分 时 日 月 周 年</code>（年可选），常用的 CRON 表达式示例：</p><ul><li><p><code>0 0 2 * * ?</code>：每天凌晨 2 点执行</p></li><li><p><code>0 0 12 * * ?</code>：每天中午 12 点执行</p></li><li><p><code>0 0 0 1 * ?</code>：每月 1 号凌晨 0 点执行</p></li><li><p><code>0 0 0 ? * MON</code>：每周一凌晨 0 点执行</p></li></ul><pre><code class="language-java">import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;// 开启定时任务，可放在启动类或配置类上@EnableScheduling@Componentpublic class SimpleJob {    // 触发规则：每天凌晨2点执行（CRON表达式）    @Scheduled(cron = &quot;0 0 2 * * ?&quot;)    public void statOrderData() {        // 业务逻辑：统计昨日订单数据，生成报表        System.out.println(&quot;执行定时任务：统计昨日订单数据，生成日报表&quot;);    }    // 触发规则：固定频率（每5秒执行，任务结束后立即计时）    @Scheduled(fixedRate = 5000)    public void asyncProcess() {        // 业务逻辑：异步处理小任务，比如清理临时文件        System.out.println(&quot;固定频率执行异步任务：清理临时文件&quot;);    }    // 触发规则：固定延迟（任务执行完成后延迟10秒再执行下一次）    @Scheduled(fixedDelay = 10000)    public void delayProcess() {        // 业务逻辑：依赖上一次执行结果的任务，比如同步数据        System.out.println(&quot;固定延迟执行任务：同步系统数据&quot;);    }}</code></pre><h5 id="2.-xxl-job%EF%BC%88%E5%88%86%E5%B8%83%E5%BC%8F%E5%9C%BA%E6%99%AF%EF%BC%89" tabindex="-1">2. XXL-Job（分布式场景）</h5><p>XXL-Job 是目前国内使用广泛的分布式任务调度框架，自带管理控制台，支持任务的分片执行、失败重试、日志追踪等功能。<br />首先需要引入 Maven 依赖：</p><pre><code class="language-xml">&lt;!-- XXL-Job核心依赖 --&gt;&lt;dependency&gt;    &lt;groupId&gt;com.xuxueli&lt;/groupId&gt;    &lt;artifactId&gt;xxl-job-core&lt;/artifactId&gt;    &lt;version&gt;2.4.0&lt;/version&gt;&lt;/dependency&gt;</code></pre><p>然后添加 XXL-Job 的配置文件（application.yml）：</p><pre><code class="language-yaml">xxl:  job:    admin:      addresses: http://127.0.0.1:8080/xxl-job-admin # XXL-Job控制台地址    executor:      appname: xxl-job-example # 执行器名称，需与控制台配置一致      address:      ip:      port: 9999 # 执行器端口      logpath: /data/applogs/xxl-job/jobhandler # 日志路径      logretentiondays: 30 # 日志保留天数    accessToken: default_token # 认证令牌，需与控制台配置一致</code></pre><p>编写执行器 Job：</p><pre><code class="language-java">import com.xxl.job.core.handler.annotation.XxlJob;import com.xxl.job.core.context.XxlJobHelper;import org.springframework.stereotype.Component;@Componentpublic class XxlJobDemo {    // 对应XXL-Job控制台配置的任务ID    @XxlJob(&quot;demoJobHandler&quot;)    public void demoJob() {        // 获取任务参数        String param = XxlJobHelper.getJobParam();        System.out.println(&quot;XXL-Job执行分布式任务，参数：&quot; + param);        // 分布式场景下的业务逻辑：比如分片处理大量数据        int shardIndex = XxlJobHelper.getShardIndex(); // 当前分片索引        int shardTotal = XxlJobHelper.getShardTotal(); // 总分片数        System.out.println(&quot;分片索引：&quot; + shardIndex + &quot;，总分片数：&quot; + shardTotal);        // 业务逻辑：根据分片索引处理对应的数据        processDataByShard(shardIndex, shardTotal);    }    private void processDataByShard(int shardIndex, int shardTotal) {        // 分片处理数据的业务逻辑        System.out.println(&quot;分片处理数据，索引：&quot; + shardIndex);    }}</code></pre><hr /><h3 id="%E4%B8%89%E3%80%81java-%E8%A7%A6%E5%8F%91-job-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%96%B9%E5%BC%8F" tabindex="-1">三、Java 触发 Job 的核心方式</h3><p>Job 的触发本质是「触发条件」的不同，核心分为 5 类：</p><h4 id="1.-%E6%97%B6%E9%97%B4%E8%A7%A6%E5%8F%91%EF%BC%88%E6%9C%80%E4%B8%BB%E6%B5%81%EF%BC%89" tabindex="-1">1. 时间触发（最主流）</h4><ul><li><p>固定频率：如每小时执行一次（<code>fixedRate</code>）、任务结束后延迟 10 分钟执行（<code>fixedDelay</code>）；适用于需要定期执行的任务，比如每小时清理一次缓存、每天同步一次数据。</p></li><li><p>CRON 表达式：精准时间调度（如每月最后一天 23 点执行、每周一凌晨 3 点执行），Spring Task/Quartz/XXL-Job 均支持；适用于有精准时间要求的任务，比如每月最后一天生成账单、每周一凌晨统计上周数据。</p></li><li><p>绝对时间：如指定 2026 年 1 月 1 日 0 点执行一次，适用于一次性的任务，比如系统升级后的数据迁移任务。</p></li></ul><h4 id="2.-%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91" tabindex="-1">2. 事件触发</h4><p>基于业务事件触发，而非时间：</p><ul><li><p>代码主动调用：订单支付成功后，调用<code>jobService.execute()</code>触发异步 Job；比如用户支付成功后，触发 Job 发送短信提醒和更新用户积分。</p></li><li><p>消息驱动：通过 MQ（RocketMQ/Kafka）触发，比如消费「订单支付成功」消息后执行 Job；适用于分布式系统中，不同服务间的事件触发，比如订单服务支付成功后发送 MQ 消息，用户服务消费消息后执行积分更新 Job。</p></li><li><p>接口触发：提供 HTTP 接口，调用接口触发 Job（如手动触发重试任务）；适用于需要手动触发的场景，比如任务执行失败后，通过接口手动重试。</p></li></ul><h4 id="3.-%E6%89%8B%E5%8A%A8%E8%A7%A6%E5%8F%91" tabindex="-1">3. 手动触发</h4><p>通过控制台 / 工具触发：</p><ul><li><p>XXL-Job/Elastic-Job 自带管理控制台，可手动点击「执行一次」触发 Job；适用于临时执行任务，比如临时统计某段时间的数据。</p></li><li><p>运维工具（如 Jenkins）手动触发批处理 Job；适用于运维场景，比如批量更新配置文件的 Job。</p></li></ul><h4 id="4.-%E4%BE%9D%E8%B5%96%E8%A7%A6%E5%8F%91" tabindex="-1">4. 依赖触发</h4><p>多 Job 存在依赖关系，前一个 Job 执行完成后触发下一个：</p><ul><li><p>示例：先执行「数据同步 Job」，同步完成后触发「数据统计 Job」，最后触发「报表生成 Job」；适用于有依赖关系的任务流，比如电商系统中，先同步订单数据，再统计订单数据，最后生成报表。</p></li><li><p>实现方式：Quartz 的 JobListener、XXL-Job 的任务依赖、Airflow 的 DAG 编排。</p></li></ul><h4 id="5.-%E5%88%86%E5%B8%83%E5%BC%8F%E8%A7%A6%E5%8F%91%EF%BC%88%E9%9B%86%E7%BE%A4%E5%9C%BA%E6%99%AF%EF%BC%89" tabindex="-1">5. 分布式触发（集群场景）</h4><p>集群环境下避免重复执行，同时保证高可用：</p><ul><li><p>Quartz 集群：基于数据库锁实现任务分发，避免重复执行；适用于传统的分布式定时任务场景。</p></li><li><p>XXL-Job：中心调度器分发任务到执行器集群，支持分片执行（如 100 万条数据拆给 5 台机器执行）；适用于大规模的分布式任务场景，比如批量处理百万级订单数据。</p></li></ul><hr /><h3 id="%E5%9B%9B%E3%80%81%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D-job-%E7%9A%84%E6%80%A7%E8%83%BD%E9%97%AE%E9%A2%98%EF%BC%9F" tabindex="-1">四、如何避免 Job 的性能问题？</h3><p>Job 是后台任务，但处理不当会导致 CPU / 内存 / 数据库耗尽，甚至拖垮整个应用，核心优化策略如下：</p><h4 id="1.-%E4%BB%BB%E5%8A%A1%E6%8B%86%E5%88%86%E4%B8%8E%E5%88%86%E6%89%B9" tabindex="-1">1. 任务拆分与分批</h4><ul><li><p>大任务拆小：将「全量数据统计」拆为「用户维度统计」+「订单维度统计」+「商品维度统计」，避免单次任务执行超时，同时可以并行执行这些小任务，提升执行效率。</p></li><li><p>分批处理数据：批量操作数据库时，每次处理 1000 条（而非一次性处理 10 万条），避免内存溢出 / 数据库锁等待。同时，在分批处理时，可以适当增加休眠时间，避免对数据库造成过大压力：</p><pre><code class="language-java">// 分批处理示例，增加休眠时间public void batchProcess() {    int pageSize = 1000;    int pageNum = 1;    while (true) {        // 分批查询数据        List&lt;Order&gt; orderList = orderMapper.selectByPage(pageNum, pageSize);        if (CollectionUtils.isEmpty(orderList)) {            break;        }        // 处理当前批次数据        processOrderList(orderList);        // 每处理完一批，休眠100毫秒，避免对数据库造成过大压力        try {            Thread.sleep(100);        } catch (InterruptedException e) {            Thread.currentThread().interrupt();            break;        }        pageNum++;    }}</code></pre></li></ul><h4 id="2.-%E8%B5%84%E6%BA%90%E6%8E%A7%E5%88%B6" tabindex="-1">2. 资源控制</h4><ul><li><p>线程池合理配置：避免使用无限线程池，核心参数（核心线程数 = CPU 核心数<em>2，最大线程数 = CPU 核心数</em>4，队列长度适中）。同时，要根据任务的类型配置不同的线程池，比如 CPU 密集型任务的核心线程数可以设置为 CPU 核心数 + 1，IO 密集型任务的核心线程数可以设置为 CPU 核心数 * 2：</p><pre><code class="language-java">// 自定义Job线程池（Spring Task），根据任务类型配置@Configurationpublic class SchedulerConfig {    @Bean    public TaskScheduler taskScheduler() {        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();        // CPU密集型任务：核心线程数=CPU核心数+1        // IO密集型任务：核心线程数=CPU核心数*2        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;        scheduler.setPoolSize(corePoolSize); // 核心线程数        scheduler.setMaxPoolSize(corePoolSize * 2); // 最大线程数        scheduler.setQueueCapacity(100); // 队列长度        scheduler.setThreadNamePrefix(&quot;job-thread-&quot;);        // 拒绝策略：使用CallerRunsPolicy，由调用线程执行任务，避免任务丢失        scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());        // 线程空闲时间：60秒        scheduler.setKeepAliveSeconds(60);        return scheduler;    }}</code></pre></li><li><p>数据库资源控制：避免 Job 执行时占用全部数据库连接，可单独配置 Job 专用连接池，比如在配置文件中设置 Job 的连接池大小为应用连接池的 1/3，避免 Job 占用过多连接影响用户请求：</p><pre><code class="language-yaml"># Job专用数据库连接池配置spring:  datasource:    job:      url: jdbc:mysql://localhost:3306/job_db      username: root      password: 123456      hikari:        maximum-pool-size: 10 # 连接池最大连接数，为应用连接池的1/3        minimum-idle: 2 # 最小空闲连接数</code></pre></li></ul><h4 id="3.-%E9%81%BF%E5%85%8D%E9%87%8D%E5%A4%8D%E6%89%A7%E8%A1%8C%EF%BC%88%E5%88%86%E5%B8%83%E5%BC%8F%E6%A0%B8%E5%BF%83%EF%BC%89" tabindex="-1">3. 避免重复执行（分布式核心）</h4><ul><li><p>分布式锁：基于 Redis/Zookeeper 实现（如 Redisson 的 RLock），在 Job 执行前先获取锁，执行完成后释放锁，避免多台机器同时执行同一个 Job：</p><pre><code class="language-java">// 基于Redisson的分布式锁示例@Autowiredprivate RedissonClient redissonClient;public void executeJob() {    // 获取分布式锁，锁的名称为Job的唯一标识    RLock lock = redissonClient.getLock(&quot;order_stat_job_lock&quot;);    try {        // 尝试获取锁，等待10秒，锁的过期时间为30秒        if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {            // 执行业务逻辑            statOrderData();        } else {            System.out.println(&quot;获取锁失败，任务已在执行中&quot;);        }    } catch (InterruptedException e) {        Thread.currentThread().interrupt();    } finally {        if (lock.isHeldByCurrentThread()) {            lock.unlock();        }    }}</code></pre></li><li><p>数据库行锁：更新「任务执行状态表」的同一行，先获取锁再执行，适用于没有 Redis/Zookeeper 的场景：</p><pre><code class="language-sql">-- 数据库行锁示例，通过UPDATE语句获取锁UPDATE job_execution_status SET status = &#39;RUNNING&#39; WHERE job_name = &#39;order_stat_job&#39; AND status = &#39;WAITING&#39;;-- 如果影响行数为1，则获取锁成功</code></pre></li><li><p>框架自带机制：XXL-Job/Quartz 的集群锁机制，无需手动实现，框架会自动处理分布式场景下的重复执行问题。</p></li></ul><h4 id="4.-%E9%81%BF%E5%BC%80%E4%B8%9A%E5%8A%A1%E9%AB%98%E5%B3%B0-%2B-%E9%99%90%E6%B5%81%E7%86%94%E6%96%AD" tabindex="-1">4. 避开业务高峰 + 限流熔断</h4><ul><li><p>核心 Job 调度在低峰期（如凌晨），避免和用户请求抢资源。比如电商系统的订单统计 Job，可以调度在凌晨 2 点到 4 点之间执行，此时用户请求量最少。</p></li><li><p>限流：对高频 Job 设置执行频率上限（如每分钟最多执行 10 次），可以通过 Guava 的 RateLimiter 实现：</p><pre><code class="language-java">// 基于Guava的限流示例private final RateLimiter rateLimiter = RateLimiter.create(10); // 每分钟最多执行10次public void executeJob() {    // 尝试获取令牌，如果获取不到则等待    if (rateLimiter.tryAcquire()) {        // 执行业务逻辑        processJob();    } else {        System.out.println(&quot;任务执行频率超过限制，跳过本次执行&quot;);    }}</code></pre></li><li><p>熔断：Job 连续失败 N 次后暂停执行，避免重试风暴（如 XXL-Job 的失败重试次数配置）。同时，可以结合熔断器模式（如 Hystrix），当 Job 失败次数达到阈值时，暂停执行一段时间，避免对系统造成过大压力。</p></li></ul><h4 id="5.-%E7%9B%91%E6%8E%A7%E4%B8%8E%E5%85%9C%E5%BA%95" tabindex="-1">5. 监控与兜底</h4><ul><li><p>监控指标：Job 执行时长、成功率、CPU / 内存占用（如 Prometheus+Grafana），可以通过 Micrometer 框架收集 Job 的监控指标，比如记录 Job 的执行时长、执行次数、失败次数等：</p><pre><code class="language-java">// 基于Micrometer的监控示例@Autowiredprivate MeterRegistry meterRegistry;public void executeJob() {    Timer.Sample sample = Timer.start(meterRegistry);    try {        // 执行业务逻辑        statOrderData();        // 记录成功次数        Counter.builder(&quot;job.execute.success&quot;)                .tag(&quot;jobName&quot;, &quot;orderStatJob&quot;)                .register(meterRegistry)                .increment();    } catch (Exception e) {        // 记录失败次数        Counter.builder(&quot;job.execute.failure&quot;)                .tag(&quot;jobName&quot;, &quot;orderStatJob&quot;)                .register(meterRegistry)                .increment();        throw e;    } finally {        // 记录执行时长        sample.stop(Timer.builder(&quot;job.execute.duration&quot;)                .tag(&quot;jobName&quot;, &quot;orderStatJob&quot;)                .register(meterRegistry));    }}</code></pre></li><li><p>异常兜底：Job 执行失败时发送告警（钉钉 / 邮件），并记录失败日志，同时可以设置自动重试机制，比如失败后重试 3 次，仍然失败则发送告警。</p></li><li><p>资源释放：Job 执行完毕后释放连接、关闭流，避免内存泄漏。比如在使用 JDBC 连接时，要确保在 finally 块中关闭连接；在使用文件流时，要确保关闭文件流。</p></li></ul><hr /><h2 id="%E4%BA%94%E3%80%81job%E5%BC%80%E5%8F%91%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5" tabindex="-1">五、Job开发的最佳实践</h2><ul><li><strong>本地测试</strong>：使用内嵌数据库（H2）或Mock框架隔离外部依赖，避免测试Job污染生产数据；可通过开关或Profile控制Job是否在本地启动；</li><li><strong>版本管理</strong>：Job代码与业务代码一同纳入Git管理，任务配置（CRON、参数）建议通过<strong>配置中心</strong>（Nacos、Apollo）动态下发，避免因配置变更而重启应用；</li><li><strong>日志链路</strong>：为每个Job执行分配唯一TraceId（如MDC.put(“traceId”, UUID.randomUUID().toString())），便于全链路追踪问题；</li><li><strong>优雅停机</strong>：Job执行中若应用关闭，需等待当前任务完成或中断后释放资源（Spring的<code>@PreDestroy</code>或实现<code>DisposableBean</code>可配合使用）。</li></ul><hr /><h3 id="%E7%BB%93%E8%AF%AD" tabindex="-1">结语</h3><p>Java Job 作为后端开发中处理非实时、后台任务的核心机制，在提升系统性能、优化业务流程方面扮演着重要的角色。从简单的单体应用定时任务，到复杂的分布式大规模数据处理，选择合适的 Job 工具与触发方式，是实现高效、稳定任务调度的关键。</p><p>本文为大家梳理了 Java Job 的基础定义、常用开发工具与技术栈、核心触发方式，以及在实际开发中避免性能问题的优化策略。在日常开发中，我们需要根据业务场景的复杂度，选择合适的框架：单体应用优先选择轻量的 Spring Task，分布式场景优先选择功能全面的 XXL-Job；同时，要注意任务的拆分与分批、合理配置资源、避免重复执行，以及做好监控与兜底工作，确保 Job 的稳定执行。</p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2026/2026-01/java-job.md" target="_blank">本文 github 仓库地址</a>，为我点一颗 Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2026 元旦低预算自驾游计划路书]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/yuan-dan-hot-spring-ski-plan" />
                <id>tag:https://fx67ll.xyz,2025-12-23:yuan-dan-hot-spring-ski-plan</id>
                <published>2025-12-23T17:20:15+08:00</published>
                <updated>2026-01-22T14:38:20+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%F0%9F%8F%94%EF%B8%8F-%E5%8D%97%E4%BA%AC%E5%85%83%E6%97%A6%E5%8D%83%E5%B2%9B%E6%B9%96%E6%B8%A9%E6%B3%89%E6%BB%91%E9%9B%AA%E4%B9%8B%E6%97%85-%7C-3-%E5%A4%A9-2-%E6%99%9A%E6%B7%B1%E5%BA%A6%E6%94%BB%E7%95%A5" tabindex="-1">🏔️ 南京元旦千岛湖温泉滑雪之旅 | 3 天 2 晚深度攻略</h1><p><a href="https://ai.fx67ll.com/yuan-dan-hot-spring-ski-plan/" target="_blank">fx67ll’s 线上攻略站点</a></p><hr /><h2 id="%F0%9F%93%8B-%E8%A1%8C%E7%A8%8B%E6%A6%82%E8%A7%88" tabindex="-1">📋 行程概览</h2><blockquote><p><strong>「 一次穿越山水与冬雪的完美周末 」</strong></p></blockquote><p>这是一条专为 <strong>电动车自驾</strong>（特别是小米 SU7 Max 用户）量身打造的冬季精品路线，集 <strong>千岛湖的灵秀、湍口温泉的暖意、大明山滑雪的刺激</strong> 于一体，让你在 3 天内体验 <strong>“湖光、暖汤、冰雪”</strong> 三重奏。</p><h3 id="%E2%9C%A8-%E4%B8%89%E5%A4%A7%E4%BA%AE%E7%82%B9" tabindex="-1">✨ 三大亮点</h3><table><thead><tr><th>项目</th><th>地点</th><th>核心体验</th><th>推荐指数</th></tr></thead><tbody><tr><td><strong>湖光山色</strong></td><td>千岛湖</td><td>中心湖区游船、绿道骑行、夜游月光岛</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><strong>温泉养生</strong></td><td>湍口温泉</td><td>1300 年历史的氡温泉、无边泳池、山景别墅</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><strong>冰雪乐趣</strong></td><td>大明山</td><td>江南地区优质滑雪场、多级别雪道、专业设施</td><td>⭐⭐⭐⭐</td></tr></tbody></table><h3 id="%F0%9F%97%93%EF%B8%8F-%E8%A1%8C%E7%A8%8B%E9%80%9F%E8%A7%88%EF%BC%883-%E5%A4%A9-2-%E6%99%9A%EF%BC%89" tabindex="-1">🗓️ 行程速览（3 天 2 晚）</h3><table><thead><tr><th>项目</th><th>详情</th></tr></thead><tbody><tr><td><strong>总里程</strong></td><td>约 800 公里</td></tr><tr><td><strong>出行方式</strong></td><td>自驾（推荐小米 SU7 Max 等电动车）</td></tr><tr><td><strong>适合人群</strong></td><td>情侣、朋友、家庭（儿童友好）</td></tr><tr><td><strong>最佳季节</strong></td><td>12 月 - 次年 2 月（滑雪季，温泉体验更佳）</td></tr><tr><td><strong>2 人总预算</strong></td><td>约 ¥3000（电动车版）</td></tr></tbody></table><hr /><h2 id="%F0%9F%97%BA%EF%B8%8F-%E8%AF%A6%E7%BB%86%E8%A1%8C%E7%A8%8B%E5%AE%89%E6%8E%92" tabindex="-1">🗺️ 详细行程安排</h2><h3 id="day-1-(%E5%91%A8%E5%85%AD)-%7C-%E5%8D%97%E4%BA%AC-%E2%86%92-%E5%8D%83%E5%B2%9B%E6%B9%96%E3%83%BB%E5%88%9D%E9%81%87%E5%8D%83%E5%B2%9B" tabindex="-1"><strong>Day 1 (周六) | 南京 → 千岛湖・初遇千岛</strong></h3><table><thead><tr><th style="text-align:left">时间</th><th style="text-align:left">活动</th><th style="text-align:left">核心内容</th><th style="text-align:left">贴士与费用</th></tr></thead><tbody><tr><td style="text-align:left"><strong>08:00 - 12:30</strong></td><td style="text-align:left"><strong>南京出发，自驾前往千岛湖</strong></td><td style="text-align:left">- 路线：沪蓉高速 → 五桥 → 宁宣高速(S55)<br>- <strong>电动车提示</strong>：小米SU7 Max满电出发，中途可视情况在服务区补电。</td><td style="text-align:left">- 距离：约365km<br>- 耗时：4-4.5h<br>- 过路费：¥180</td></tr><tr><td style="text-align:left"><strong>13:00 - 15:30</strong></td><td style="text-align:left"><strong>千岛湖中心湖区游览</strong></td><td style="text-align:left">1. <strong>梅峰岛</strong>：乘缆车登顶，俯瞰“千岛”全景，最佳拍照点。<br>2. <strong>渔乐岛</strong>：体验淳安渔家文化。</td><td style="text-align:left">- <strong>淡季福利</strong>：景区大门票免费<br>- 游船票：¥45/人<br>- 缆车票：可选，约¥60/人</td></tr><tr><td style="text-align:left"><strong>16:00 - 19:00</strong></td><td style="text-align:left"><strong>双人浪漫夜游月光岛</strong></td><td style="text-align:left">乘坐游船，欣赏湖上落日，观看“月光之恋”主题光影秀，浪漫氛围拉满。</td><td style="text-align:left">- 双人套票：¥130/2人<br>- 建议带件厚外套，湖上傍晚较凉。</td></tr><tr><td style="text-align:left"><strong>19:30 - 20:30</strong></td><td style="text-align:left"><strong>品尝千岛湖鱼头宴</strong></td><td style="text-align:left"><strong>餐厅推荐</strong>：<br>1. <strong>鱼味馆</strong>（老字号，味道正宗）<br>2. <strong>淳圆外</strong>（环境雅致，临湖而建）<br><strong>必点菜</strong>：千岛湖有机鱼头汤（奶白鲜香）、清蒸白花鱼。</td><td style="text-align:left">- 人均：¥100-150<br>- 鱼头汤建议提前电话预订。</td></tr><tr><td style="text-align:left"><strong>21:00</strong></td><td style="text-align:left"><strong>入住湖景酒店</strong></td><td style="text-align:left"><strong>酒店推荐</strong>：<strong>voco千岛湖阳光大酒店</strong><br>- 位于独立小岛，三面环湖，环境私密。<br>- 晨起在阳台即可欣赏湖光山色。</td><td style="text-align:left">- 参考价：湖景房 ¥376/晚<br>- <strong>免费停车</strong>，部分车位带充电桩。</td></tr></tbody></table><h4 id="%F0%9F%95%98-08%3A00---12%3A30-%E5%8D%97%E4%BA%AC%E5%87%BA%E5%8F%91%EF%BC%8C%E8%87%AA%E9%A9%BE%E5%89%8D%E5%BE%80%E5%8D%83%E5%B2%9B%E6%B9%96" tabindex="-1">🕘 08:00 - 12:30 南京出发，自驾前往千岛湖</h4><p><strong>路线规划</strong>：</p><pre><code class="language-">南京市区 → 宁宣高速(S55) → 宣桐高速(S05) → 溧宁高速(G40) → 千岛湖出口</code></pre><p><strong>驾驶攻略</strong>：</p><ul><li><strong>距离</strong>：约 365 公里</li><li><strong>耗时</strong>：4-4.5 小时</li><li><strong>过路费</strong>：¥180</li><li><strong>电动车提示</strong>：小米 SU7 Max 满电出发，宣城东服务区可补电 30 分钟</li></ul><h4 id="%F0%9F%95%92-13%3A00---15%3A30-%E5%8D%83%E5%B2%9B%E6%B9%96%E4%B8%AD%E5%BF%83%E6%B9%96%E5%8C%BA%E6%B8%B8%E8%A7%88" tabindex="-1">🕒 13:00 - 15:30 千岛湖中心湖区游览</h4><p><strong>游船体验</strong>：</p><ul><li><strong>游船时间</strong>：13:30 开船，全程约 2.5 小时</li><li><strong>登岛顺序</strong>：梅峰岛 → 渔乐岛</li><li><strong>游船类型</strong>：豪华游船，有空调和观景甲板</li></ul><p><strong>梅峰岛深度体验</strong>：</p><ul><li><strong>缆车体验</strong>：往返 ¥60 / 人，建议乘坐</li><li><strong>观景台</strong>：多个观景台，360 度欣赏湖景</li><li><strong>最佳拍照点</strong>：山顶观景台，可拍摄千岛湖经典全景</li><li><strong>游览时间</strong>：建议 1 小时</li></ul><p><strong>渔乐岛体验</strong>：</p><ul><li><strong>渔家文化</strong>：体验传统渔家生活</li><li><strong>现烤鱼干</strong>：新鲜制作的千岛湖鱼干</li><li><strong>休闲时光</strong>：岛上有休息区，可稍作休息</li><li><strong>游览时间</strong>：建议 45 分钟</li></ul><p><strong>门票信息</strong>：</p><ul><li>千岛湖景区门票：淡季免费（12 月 - 次年 2 月）</li><li>游船票：¥45 / 人</li><li>梅峰岛缆车：¥60 / 人（往返）</li></ul><h4 id="%F0%9F%95%95-16%3A00---19%3A00-%E5%8F%8C%E4%BA%BA%E6%B5%AA%E6%BC%AB%E5%A4%9C%E6%B8%B8%E6%9C%88%E5%85%89%E5%B2%9B" tabindex="-1">🕕 16:00 - 19:00 双人浪漫夜游月光岛</h4><p><strong>夜游特色</strong>：</p><ul><li><strong>游船体验</strong>：小型游船，更具私密性</li><li><strong>落日观赏</strong>：17:00-17:30 是观赏落日的最佳时间</li><li><strong>灯光秀</strong>：月光之恋灯光展览，浪漫唯美</li><li><strong>音乐体验</strong>：船上有轻柔音乐，营造浪漫氛围</li></ul><p><strong>费用</strong>：双人套票 ¥130/2 人（含游船 + 月光岛门票）</p><h4 id="%F0%9F%95%A2-19%3A30---20%3A30-%E5%93%81%E5%B0%9D%E5%8D%83%E5%B2%9B%E6%B9%96%E9%B1%BC%E5%A4%B4%E5%AE%B4" tabindex="-1">🕢 19:30 - 20:30 品尝千岛湖鱼头宴</h4><p><strong>餐厅推荐</strong>：</p><p><strong>🍴 千岛湖鱼味馆</strong>（推荐指数：⭐⭐⭐⭐⭐）</p><ul><li><strong>地址</strong>：千岛湖镇排岭南路 2 号</li><li><strong>特色</strong>：30 年老字号，正宗千岛湖鱼头</li><li><strong>必点菜品</strong>：<ul><li>千岛湖有机鱼头汤（¥168 / 锅，足够 2-3 人）</li><li>清蒸白花鱼（¥88 / 份）</li><li>秀水螺蛳（¥38 / 份）</li></ul></li><li><strong>人均</strong>：¥120-150</li><li><strong>预订</strong>：建议提前预订，特别是周末</li></ul><p><strong>🍴 淳圆外寻味古村落</strong>（推荐指数：⭐⭐⭐⭐）</p><ul><li><strong>地址</strong>：千岛湖镇睦州大道</li><li><strong>特色</strong>：徽派建筑，临湖而建</li><li><strong>人均</strong>：¥100-130</li></ul><h4 id="%F0%9F%95%98-21%3A00-%E5%85%A5%E4%BD%8F%E6%B9%96%E6%99%AF%E9%85%92%E5%BA%97" tabindex="-1">🕘 21:00 入住湖景酒店</h4><p><strong>🏨 voco 千岛湖阳光大酒店</strong></p><ul><li><strong>地理位置</strong>：独占千岛湖 1078 座岛屿之一，三面环水</li><li><strong>建筑风格</strong>：现代简约风格，与自然完美融合</li><li><strong>客房设施</strong>：湖景房落地窗，湖景一览无余</li><li><strong>酒店设施</strong>：室外泳池、健身房、SPA、免费停车</li><li><strong>参考价</strong>：湖景房 ¥376 / 晚</li></ul><h3 id="day-2-(%E5%91%A8%E6%97%A5)-%7C-%E5%8D%83%E5%B2%9B%E6%B9%96%E6%B7%B1%E5%BA%A6%E6%B8%B8-%E2%86%92-%E6%B9%8D%E5%8F%A3%E6%B8%A9%E6%B3%89%E3%83%BB%E6%9A%96%E6%84%8F%E6%97%B6%E5%85%89" tabindex="-1"><strong>Day 2 (周日) | 千岛湖深度游 → 湍口温泉・暖意时光</strong></h3><table><thead><tr><th style="text-align:left">时间</th><th style="text-align:left">活动</th><th style="text-align:left">核心内容</th><th style="text-align:left">贴士与费用</th></tr></thead><tbody><tr><td style="text-align:left"><strong>08:00 - 09:00</strong></td><td style="text-align:left"><strong>酒店湖景早餐</strong></td><td style="text-align:left">在voco酒店享用中西式自助早餐，面对波光粼粼的湖面，开启惬意一天。</td><td style="text-align:left">- 早餐通常含在房费内。</td></tr><tr><td style="text-align:left"><strong>09:30 - 11:30</strong></td><td style="text-align:left"><strong>千岛湖绿道骑行</strong></td><td style="text-align:left">- <strong>最美路段</strong>：千岛湖大桥 → 红叶湾 → 小金山大桥。<br>- 沿途设有多个观景台，随时停车拍照。租用双人自行车，乐趣加倍。</td><td style="text-align:left">- 租车费：约¥30/小时/辆<br>- 全程约15km，坡度平缓，轻松休闲。</td></tr><tr><td style="text-align:left"><strong>12:00 - 13:00</strong></td><td style="text-align:left"><strong>品尝地道农家菜</strong></td><td style="text-align:left"><strong>餐厅推荐</strong>：<strong>好灶头妈妈菜</strong><br>- 临湖餐厅，品尝地道土鸡煲、野笋等农家风味。</td><td style="text-align:left">- 人均：¥80-100</td></tr><tr><td style="text-align:left"><strong>13:30 - 15:00</strong></td><td style="text-align:left"><strong>自驾前往湍口温泉</strong></td><td style="text-align:left">- 路线：千汾线 → 昌文线（山路）。<br>- <strong>驾驶提示</strong>：山路弯道多，风景美，请谨慎驾驶，注意安全。</td><td style="text-align:left">- 距离：约70km<br>- 耗时：1-1.5h</td></tr><tr><td style="text-align:left"><strong>15:30 - 17:30</strong></td><td style="text-align:left"><strong>入住温泉度假村</strong></td><td style="text-align:left"><strong>酒店推荐</strong>：<strong>湍口众安汇尊温泉度假村</strong><br>- 巴厘岛风格建筑，被群山环绕。<br>- 办理入住后，可先逛逛度假村内的萌宠乐园、无边泳池。</td><td style="text-align:left">- 参考价：套餐价 ¥999/晚（<strong>含无限次温泉+双人自助早晚餐</strong>）</td></tr><tr><td style="text-align:left"><strong>18:00 - 19:00</strong></td><td style="text-align:left"><strong>酒店自助晚餐</strong></td><td style="text-align:left">度假村内自助餐厅，菜品丰富，可尽情享用。</td><td style="text-align:left">- 品质较高，物有所值。</td></tr><tr><td style="text-align:left"><strong>19:30 - 22:00</strong></td><td style="text-align:left"><strong>沉浸式温泉体验</strong></td><td style="text-align:left">- <strong>温泉特色</strong>：千年“芦荻泉”，属<strong>碳酸钙型氡温泉</strong>，有良好的保健疗养价值。<br>- 体验多个不同功能的泡池，在星空下放松身心。</td><td style="text-align:left">- <strong>必体验</strong>：山顶无边温泉池，拍照绝佳。<br>- <strong>注意</strong>：每次泡汤不超过15分钟，及时补水。</td></tr></tbody></table><h4 id="%F0%9F%95%97-08%3A00---09%3A00-%E9%85%92%E5%BA%97%E6%B9%96%E6%99%AF%E6%97%A9%E9%A4%90" tabindex="-1">🕗 08:00 - 09:00 酒店湖景早餐</h4><p><strong>早餐体验</strong>：</p><ul><li><strong>餐厅位置</strong>：酒店一层，湖景餐厅</li><li><strong>早餐类型</strong>：中西式自助</li><li><strong>特色菜品</strong>：千岛湖特色小吃、现做 omelette、新鲜水果</li><li><strong>用餐时间</strong>：建议 7:30-8:30，避开高峰</li></ul><h4 id="%F0%9F%95%98-09%3A30---11%3A30-%E5%8D%83%E5%B2%9B%E6%B9%96%E7%BB%BF%E9%81%93%E9%AA%91%E8%A1%8C%E4%BD%93%E9%AA%8C" tabindex="-1">🕘 09:30 - 11:30 千岛湖绿道骑行体验</h4><p><strong>骑行攻略</strong>：</p><ul><li><strong>租车地点</strong>：千岛湖大桥附近的自行车租赁点</li><li><strong>车型选择</strong>：<ul><li>单人自行车：¥30 / 小时</li><li>双人自行车：¥50 / 小时</li><li>电动自行车：¥80 / 小时</li></ul></li><li><strong>路线规划</strong>：千岛湖大桥 → 红叶湾 → 小金山大桥（约 15 公里）</li><li><strong>骑行时间</strong>：建议 2 小时，不紧不慢</li></ul><p><strong>骑行体验</strong>：</p><ul><li><strong>绿道特色</strong>：专用骑行道，安全舒适</li><li><strong>风景亮点</strong>：千岛湖大桥、红叶湾、小金山大桥</li><li><strong>休息点</strong>：沿途有多个休息点和观景台</li><li><strong>拍照点</strong>：每个观景台都是拍照的好地方</li></ul><h4 id="%F0%9F%95%9B-12%3A00---13%3A00-%E5%93%81%E5%B0%9D%E5%9C%B0%E9%81%93%E5%86%9C%E5%AE%B6%E8%8F%9C" tabindex="-1">🕛 12:00 - 13:00 品尝地道农家菜</h4><p><strong>🍴 好灶头妈妈菜</strong>（推荐指数：⭐⭐⭐⭐）</p><ul><li><strong>地址</strong>：千岛湖镇环湖北路</li><li><strong>特色</strong>：地道农家菜，临湖而坐</li><li><strong>必点菜品</strong>：<ul><li>农家土鸡汤（¥88 / 份）</li><li>炒山笋（¥38 / 份）</li><li>农家豆腐（¥28 / 份）</li></ul></li><li><strong>人均</strong>：¥80</li></ul><h4 id="%F0%9F%95%90-13%3A30---15%3A00-%E8%87%AA%E9%A9%BE%E5%89%8D%E5%BE%80%E6%B9%8D%E5%8F%A3%E6%B8%A9%E6%B3%89" tabindex="-1">🕐 13:30 - 15:00 自驾前往湍口温泉</h4><p><strong>路线规划</strong>：</p><pre><code class="language-">千岛湖镇 → 千汾线 → 昌文线 → 湍口镇</code></pre><p><strong>驾驶攻略</strong>：</p><ul><li><strong>距离</strong>：约 70 公里</li><li><strong>耗时</strong>：1-1.5 小时</li><li><strong>路况</strong>：山路，弯道较多</li><li><strong>风景</strong>：沿途山区风景优美，特别是冬季</li></ul><h4 id="%F0%9F%95%92-15%3A30---17%3A30-%E5%85%A5%E4%BD%8F%E6%B8%A9%E6%B3%89%E5%BA%A6%E5%81%87%E6%9D%91" tabindex="-1">🕒 15:30 - 17:30 入住温泉度假村</h4><p><strong>🏨 湍口众安汇尊温泉度假村</strong></p><ul><li><strong>建筑风格</strong>：巴厘岛风格，热带风情</li><li><strong>地理位置</strong>：山区深处，环境幽静</li><li><strong>客房选择</strong>：豪华客房、私人别墅、豪华 LOFT</li><li><strong>酒店设施</strong>：温泉中心、水乐园、萌宠乐园、SPA</li><li><strong>参考价</strong>：¥999 / 晚（含温泉 + 自助早晚餐）</li></ul><h4 id="%F0%9F%95%95-18%3A00---19%3A00-%E9%85%92%E5%BA%97%E8%87%AA%E5%8A%A9%E6%99%9A%E9%A4%90" tabindex="-1">🕕 18:00 - 19:00 酒店自助晚餐</h4><p><strong>晚餐体验</strong>：</p><ul><li><strong>餐厅位置</strong>：酒店一层，自助餐厅</li><li><strong>晚餐类型</strong>：中西式自助</li><li><strong>特色菜品</strong>：海鲜区、热菜区、烧烤区、甜品区</li><li><strong>用餐建议</strong>：不要吃得太饱，以免影响温泉体验</li></ul><h4 id="%F0%9F%95%A2-19%3A30---22%3A00-%E6%B2%89%E6%B5%B8%E5%BC%8F%E6%B8%A9%E6%B3%89%E4%BD%93%E9%AA%8C" tabindex="-1">🕢 19:30 - 22:00 沉浸式温泉体验</h4><p><strong>温泉深度体验</strong>：</p><ul><li><strong>历史渊源</strong>：源自 1300 年历史的 “芦荻泉”</li><li><strong>水质特点</strong>：碳酸钙型热矿泉，富含氡元素</li><li><strong>医疗价值</strong>：对关节炎、皮肤病等有一定疗效</li><li><strong>温泉温度</strong>：38-42℃，适宜人体</li></ul><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><ul><li><strong>体验顺序</strong>：先从低温池开始，逐渐适应</li><li><strong>浸泡时间</strong>：每个池不超过 15 分钟</li><li><strong>休息间隔</strong>：浸泡后在休息区休息 10-15 分钟</li><li><strong>饮水建议</strong>：及时补充水分，避免脱水</li></ul><h3 id="day-3-(%E5%91%A8%E4%B8%80)-%7C-%E6%B9%8D%E5%8F%A3-%E2%86%92-%E5%A4%A7%E6%98%8E%E5%B1%B1%E6%BB%91%E9%9B%AA-%E2%86%92-%E5%8D%97%E4%BA%AC%E3%83%BB%E5%86%B0%E9%9B%AA%E7%BB%88%E7%AB%A0" tabindex="-1"><strong>Day 3 (周一) | 湍口 → 大明山滑雪 → 南京・冰雪终章</strong></h3><table><thead><tr><th style="text-align:left">时间</th><th style="text-align:left">活动</th><th style="text-align:left">核心内容</th><th style="text-align:left">贴士与费用</th></tr></thead><tbody><tr><td style="text-align:left"><strong>08:00 - 09:00</strong></td><td style="text-align:left"><strong>酒店自助早餐</strong></td><td style="text-align:left">享用丰盛早餐，为滑雪储备充足能量。</td><td style="text-align:left">- 建议多吃碳水化合物。</td></tr><tr><td style="text-align:left"><strong>09:30 - 10:30</strong></td><td style="text-align:left"><strong>自驾前往大明山滑雪场</strong></td><td style="text-align:left">- 路线：昌文线 → 和白线，路况良好的盘山公路。<br>- <strong>电动车提示</strong>：出发前确认电量充足，滑雪场有充电桩。</td><td style="text-align:left">- 距离：约45km<br>- 耗时：1-1.5h</td></tr><tr><td style="text-align:left"><strong>11:00 - 18:00</strong></td><td style="text-align:left"><strong>大明山滑雪狂欢</strong></td><td style="text-align:left">1. <strong>雪道选择</strong>：练习道（新手）、初级道、中级道。<br>2. <strong>费用包含</strong>：雪鞋、雪板、雪杖。<br>3. <strong>建议</strong>：初学者可聘请教练（¥300/小时），快速掌握基础，安全又高效。</td><td style="text-align:left">- <strong>工作日票价</strong>：滑雪票 ¥250/双人<br>- <strong>景区交通</strong>：索道往返 ¥320/双人<br>- <strong>其他消费</strong>：储物柜、滑雪服租赁等。</td></tr><tr><td style="text-align:left"><strong>18:30</strong></td><td style="text-align:left"><strong>启程返回南京</strong></td><td style="text-align:left">带着满满的美好回忆，安全返程。</td><td style="text-align:left">- 距离：约300km<br>- 耗时：3-3.5h<br>- 过路费：¥140<br>- <strong>建议</strong>：返程途中可在服务区用餐休息。</td></tr></tbody></table><h4 id="%F0%9F%95%97-08%3A00---09%3A00-%E9%85%92%E5%BA%97%E8%87%AA%E5%8A%A9%E6%97%A9%E9%A4%90" tabindex="-1">🕗 08:00 - 09:00 酒店自助早餐</h4><p><strong>早餐体验</strong>：</p><ul><li><strong>餐厅位置</strong>：酒店一层，自助餐厅</li><li><strong>早餐类型</strong>：中西式自助</li><li><strong>特色菜品</strong>：现做面条、中式点心、西式面包、新鲜水果</li><li><strong>用餐建议</strong>：多吃一些，为滑雪储备能量</li></ul><h4 id="%F0%9F%95%98-09%3A30---10%3A30-%E8%87%AA%E9%A9%BE%E5%89%8D%E5%BE%80%E5%A4%A7%E6%98%8E%E5%B1%B1%E6%BB%91%E9%9B%AA%E5%9C%BA" tabindex="-1">🕘 09:30 - 10:30 自驾前往大明山滑雪场</h4><p><strong>路线规划</strong>：</p><pre><code class="language-">湍口镇 → 昌文线 → 和白线 → 大明山景区</code></pre><p><strong>驾驶攻略</strong>：</p><ul><li><strong>距离</strong>：约 45 公里</li><li><strong>耗时</strong>：1-1.5 小时</li><li><strong>路况</strong>：山路，路况良好</li><li><strong>风景</strong>：沿途山区风景优美，冬季有雪景</li></ul><h4 id="%F0%9F%95%9A-11%3A00---18%3A00-%E5%A4%A7%E6%98%8E%E5%B1%B1%E6%BB%91%E9%9B%AA%E7%8B%82%E6%AC%A2" tabindex="-1">🕚 11:00 - 18:00 大明山滑雪狂欢</h4><p><strong>滑雪深度体验</strong>：</p><ul><li><strong>规模</strong>：江南地区最大的滑雪场</li><li><strong>海拔</strong>：1200 米，雪质好</li><li><strong>雪道</strong>：练习道、初级道、中级道</li><li><strong>设施</strong>：进口滑雪设备，安全有保障</li></ul><p><strong>滑雪装备</strong>：</p><ul><li><strong>必备装备</strong>：滑雪服、滑雪裤、头盔、手套、护目镜</li><li><strong>租赁费用</strong>：<ul><li>滑雪服：¥50 / 天</li><li>滑雪裤：¥30 / 天</li><li>头盔：¥20 / 天</li><li>手套：¥10 / 天</li><li>护目镜：¥20 / 天</li></ul></li></ul><p><strong>滑雪课程</strong>：</p><ul><li><strong>教练费用</strong>：¥300 / 小时（1 对 1）</li><li><strong>教学内容</strong>：基本姿势、刹车技巧、转弯技巧、摔倒技巧</li><li><strong>学习时间</strong>：建议 2-3 小时，基本掌握滑雪技巧</li></ul><p><strong>费用信息</strong>：</p><ul><li>工作日双人滑雪票：¥250</li><li>索道双人来回：¥320</li><li>教练：¥300 / 小时（按需选择）</li><li>装备租赁：约 ¥130 / 人 / 天（按需选择）</li></ul><h4 id="%F0%9F%95%A1-18%3A30-%E5%90%AF%E7%A8%8B%E8%BF%94%E5%9B%9E%E5%8D%97%E4%BA%AC" tabindex="-1">🕡 18:30 启程返回南京</h4><p><strong>路线规划</strong>：</p><pre><code class="language-">大明山 → 杭瑞高速(G56) → 宣桐高速(S05) → 宁宣高速(S55) → 南京市区</code></pre><p><strong>驾驶攻略</strong>：</p><ul><li><strong>距离</strong>：约 300 公里</li><li><strong>耗时</strong>：3-3.5 小时</li><li><strong>过路费</strong>：¥140</li><li><strong>电费</strong>：约 ¥80（电动车）</li></ul><hr /><h2 id="%F0%9F%8F%A8-%E4%BD%8F%E5%AE%BF%E6%8E%A8%E8%8D%90" tabindex="-1">🏨 住宿推荐</h2><table><thead><tr><th style="text-align:left">酒店</th><th style="text-align:left">特色</th><th style="text-align:left">参考价格 (晚)</th><th style="text-align:left">推荐理由</th></tr></thead><tbody><tr><td style="text-align:left"><strong>voco千岛湖阳光大酒店</strong></td><td style="text-align:left">独占一岛，270°湖景，设计时尚。</td><td style="text-align:left">¥376 (湖景房)</td><td style="text-align:left">地理位置绝佳，私密性好，早餐景观无敌，是千岛湖住宿的品质之选。</td></tr><tr><td style="text-align:left"><strong>湍口众安汇尊温泉度假村</strong></td><td style="text-align:left">巴厘岛风格，氡温泉入户，别墅私密。</td><td style="text-align:left">¥999 (套餐价)</td><td style="text-align:left"><strong>强烈推荐套餐</strong>！含温泉和双早双晚，性价比极高，一站式度假体验。</td></tr></tbody></table><h3 id="%F0%9F%8F%86-%E7%B2%BE%E9%80%89%E9%85%92%E5%BA%97%E6%B7%B1%E5%BA%A6%E5%88%86%E6%9E%90" tabindex="-1">🏆 精选酒店深度分析</h3><h4 id="%F0%9F%8C%9F-voco-%E5%8D%83%E5%B2%9B%E6%B9%96%E9%98%B3%E5%85%89%E5%A4%A7%E9%85%92%E5%BA%97" tabindex="-1">🌟 voco 千岛湖阳光大酒店</h4><p><strong>酒店概况</strong>：</p><ul><li><strong>品牌</strong>：洲际酒店集团旗下 voco 品牌</li><li><strong>星级</strong>：五星级标准</li><li><strong>开业时间</strong>：2021 年</li><li><strong>房间数量</strong>：200 间</li><li><strong>位置优势</strong>：独占千岛湖 1078 座岛屿之一，三面环水</li></ul><p><strong>客房分析</strong>：</p><table><thead><tr><th>房型</th><th>价格</th><th>面积</th><th>特色</th><th>推荐指数</th></tr></thead><tbody><tr><td><strong>湖景房</strong></td><td>¥376 / 晚</td><td>40㎡</td><td>落地窗，湖景一览无余</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><strong>山景房</strong></td><td>¥326 / 晚</td><td>40㎡</td><td>安静舒适，山景优美</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>湖景套房</strong></td><td>¥888 / 晚</td><td>80㎡</td><td>超大空间，豪华设施</td><td>⭐⭐⭐⭐</td></tr></tbody></table><p><strong>设施体验</strong>：</p><ul><li><strong>室外泳池</strong>：夏季开放，湖景泳池</li><li><strong>健身房</strong>：设备齐全，24 小时开放</li><li><strong>SPA 中心</strong>：专业 SPA 服务，放松身心</li><li><strong>餐厅</strong>：湖景餐厅，提供中西式美食</li><li><strong>免费停车</strong>：充足停车位，免费停车</li></ul><h4 id="%F0%9F%8C%9F-%E6%B9%8D%E5%8F%A3%E4%BC%97%E5%AE%89%E6%B1%87%E5%B0%8A%E6%B8%A9%E6%B3%89%E5%BA%A6%E5%81%87%E6%9D%91" tabindex="-1">🌟 湍口众安汇尊温泉度假村</h4><p><strong>酒店概况</strong>：</p><ul><li><strong>品牌</strong>：众安集团旗下高端温泉品牌</li><li><strong>星级</strong>：五星级标准</li><li><strong>开业时间</strong>：2015 年</li><li><strong>房间数量</strong>：150 间</li><li><strong>位置优势</strong>：山区深处，环境幽静</li></ul><p><strong>客房分析</strong>：</p><table><thead><tr><th>房型</th><th>价格</th><th>面积</th><th>特色</th><th>推荐指数</th></tr></thead><tbody><tr><td><strong>豪华客房</strong></td><td>¥999 / 晚</td><td>50㎡</td><td>舒适宽敞，山景优美</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><strong>私人别墅</strong></td><td>¥2888 / 晚</td><td>150㎡</td><td>独立空间，专属温泉池</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>豪华 LOFT</strong></td><td>¥1288 / 晚</td><td>80㎡</td><td>两层设计，空间更大</td><td>⭐⭐⭐⭐</td></tr></tbody></table><p><strong>设施体验</strong>：</p><ul><li><strong>温泉中心</strong>：多个温泉池，功能各异</li><li><strong>水乐园</strong>：适合儿童，家庭友好</li><li><strong>萌宠乐园</strong>：各种可爱动物，适合亲子</li><li><strong>SPA 中心</strong>：专业 SPA 服务，放松身心</li><li><strong>餐厅</strong>：自助餐厅，品种丰富</li></ul><hr /><h2 id="%F0%9F%8D%BD%EF%B8%8F-%E7%BE%8E%E9%A3%9F%E6%94%BB%E7%95%A5" tabindex="-1">🍽️ 美食攻略</h2><h3 id="%F0%9F%8D%B4-%E5%8D%83%E5%B2%9B%E6%B9%96%E5%BF%85%E5%90%83%E6%A6%9C" tabindex="-1">🍴 千岛湖必吃榜</h3><ol><li><strong>千岛湖有机鱼头汤</strong>：灵魂菜品，汤色奶白，鲜美无比。推荐餐厅：<strong>鱼味馆</strong>。</li><li><strong>清蒸白花鱼</strong>：品尝湖鲜的原汁原味。</li><li><strong>农家土菜</strong>：土鸡煲、野笋、粉皮。推荐餐厅：<strong>好灶头妈妈菜</strong>。</li></ol><h4 id="%F0%9F%90%9F-%E5%8D%83%E5%B2%9B%E6%B9%96%E9%B1%BC%E5%A4%B4%EF%BC%9A%E4%B8%8D%E5%8F%AF%E9%94%99%E8%BF%87%E7%9A%84%E6%8B%9B%E7%89%8C%E8%8F%9C" tabindex="-1">🐟 千岛湖鱼头：不可错过的招牌菜</h4><p><strong>鱼头文化</strong>：</p><ul><li><strong>历史渊源</strong>：千岛湖鱼头已有 30 多年历史</li><li><strong>选材标准</strong>：选用 7 斤以上的有机鳙鱼</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>：适合 2 人食用</li></ul><p><strong>制作方法</strong>：</p><ul><li><strong>传统炖法</strong>：慢火炖制，汤色奶白</li><li><strong>剁椒鱼头</strong>：辣味十足，适合喜欢辣的人</li><li><strong>清蒸鱼头</strong>：原汁原味，清淡健康</li></ul><h3 id="%F0%9F%8D%B4-%E9%A4%90%E5%8E%85%E5%AF%B9%E6%AF%94" tabindex="-1">🍴 餐厅对比</h3><table><thead><tr><th style="text-align:left">餐厅</th><th style="text-align:left">类型</th><th style="text-align:left">人均</th><th style="text-align:left">特色</th></tr></thead><tbody><tr><td style="text-align:left"><strong>鱼味馆</strong></td><td style="text-align:left">老字号酒楼</td><td style="text-align:left">¥120-150</td><td style="text-align:left">鱼头做法最正宗，名气大，适合打卡。</td></tr><tr><td style="text-align:left"><strong>淳圆外</strong></td><td style="text-align:left">景观餐厅</td><td style="text-align:left">¥100-130</td><td style="text-align:left">徽派建筑，临湖而建，环境优雅，适合约会。</td></tr><tr><td style="text-align:left"><strong>好灶头</strong></td><td style="text-align:left">农家菜</td><td style="text-align:left">¥80-100</td><td style="text-align:left">锅气足，味道地道，性价比高。</td></tr></tbody></table><h3 id="%F0%9F%8D%B4-%E6%8E%A8%E8%8D%90%E9%A4%90%E5%8E%85%E6%B7%B1%E5%BA%A6%E8%AF%84%E6%B5%8B" tabindex="-1">🍴 推荐餐厅深度评测</h3><h4 id="%F0%9F%8C%9F-%E5%8D%83%E5%B2%9B%E6%B9%96%E9%B1%BC%E5%91%B3%E9%A6%86%EF%BC%8830-%E5%B9%B4%E8%80%81%E5%AD%97%E5%8F%B7%EF%BC%89" tabindex="-1">🌟 千岛湖鱼味馆（30 年老字号）</h4><p><strong>餐厅概况</strong>：</p><ul><li><strong>开业时间</strong>：1980 年代</li><li><strong>位置</strong>：千岛湖镇排岭南路 2 号</li><li><strong>环境</strong>：宽敞明亮，有湖景座位</li><li><strong>规模</strong>：可容纳 300 人同时用餐</li></ul><p><strong>菜品评测</strong>：</p><table><thead><tr><th>菜品</th><th>价格</th><th>分量</th><th>口味</th><th>推荐指数</th></tr></thead><tbody><tr><td><strong>千岛湖有机鱼头汤</strong></td><td>¥168 / 锅</td><td>大份</td><td>鲜美</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><strong>清蒸白花鱼</strong></td><td>¥88 / 份</td><td>中份</td><td>清淡</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>秀水螺蛳</strong></td><td>¥38 / 份</td><td>小份</td><td>鲜美</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>农家土鸡汤</strong></td><td>¥88 / 份</td><td>中份</td><td>香浓</td><td>⭐⭐⭐⭐</td></tr></tbody></table><p><strong>服务评价</strong>：</p><ul><li><strong>服务态度</strong>：热情周到</li><li><strong>上菜速度</strong>：较快</li><li><strong>环境清洁</strong>：干净整洁</li><li><strong>性价比</strong>：较高</li></ul><h4 id="%F0%9F%8C%9F-%E6%B7%B3%E5%9C%86%E5%A4%96%E5%AF%BB%E5%91%B3%E5%8F%A4%E6%9D%91%E8%90%BD" tabindex="-1">🌟 淳圆外寻味古村落</h4><p><strong>餐厅概况</strong>：</p><ul><li><strong>开业时间</strong>：2010 年代</li><li><strong>位置</strong>：千岛湖镇睦州大道</li><li><strong>环境</strong>：徽派建筑，临湖而建</li><li><strong>规模</strong>：可容纳 200 人同时用餐</li></ul><p><strong>菜品评测</strong>：</p><table><thead><tr><th>菜品</th><th>价格</th><th>分量</th><th>口味</th><th>推荐指数</th></tr></thead><tbody><tr><td><strong>千岛湖鱼头汤</strong></td><td>¥128 / 锅</td><td>中份</td><td>鲜美</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>徽州臭鳜鱼</strong></td><td>¥98 / 份</td><td>中份</td><td>独特</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>农家小炒肉</strong></td><td>¥48 / 份</td><td>小份</td><td>香辣</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>清炒时蔬</strong></td><td>¥28 / 份</td><td>小份</td><td>清淡</td><td>⭐⭐⭐</td></tr></tbody></table><h3 id="%F0%9F%8D%B4-%E5%85%B6%E4%BB%96%E7%BE%8E%E9%A3%9F%E6%8E%A8%E8%8D%90" tabindex="-1">🍴 其他美食推荐</h3><h4 id="%F0%9F%8D%9C-%E5%8D%83%E5%B2%9B%E6%B9%96%E7%89%B9%E8%89%B2%E5%B0%8F%E5%90%83" tabindex="-1">🍜 千岛湖特色小吃</h4><ul><li><strong>千岛湖粉皮</strong>：当地特色，口感爽滑，可炒可汤，¥28 / 份</li><li><strong>千岛湖米粿</strong>：传统小吃，香甜可口，有多种口味选择，¥10 / 份</li><li><strong>千岛湖鱼干</strong>：新鲜制作，味道鲜美，可直接食用或炒菜，¥50 / 斤</li></ul><h4 id="%F0%9F%8D%B9-%E9%A5%AE%E5%93%81%E6%8E%A8%E8%8D%90" tabindex="-1">🍹 饮品推荐</h4><ul><li><strong>千岛湖啤酒</strong>：当地特产，口感清爽，有多种口味选择，¥15 / 瓶</li><li><strong>千岛玉叶茶</strong>：当地名茶，清香回甘，适合饭后饮用，¥58 / 壶</li></ul><hr /><h2 id="%F0%9F%9A%97-%E4%BA%A4%E9%80%9A%E4%B8%8E%E8%B4%B9%E7%94%A8%E8%AF%A6%E8%A7%A3%EF%BC%88%E5%B0%8F%E7%B1%B3-su7-max-%E7%89%88%EF%BC%89" tabindex="-1">🚗 交通与费用详解（小米 SU7 Max 版）</h2><h3 id="%F0%9F%9B%A3%EF%B8%8F-%E8%AF%A6%E7%BB%86%E8%B7%AF%E7%BA%BF%E8%A7%84%E5%88%92" tabindex="-1">🛣️ 详细路线规划</h3><h4 id="%F0%9F%97%BA%EF%B8%8F-%E5%85%A8%E7%A8%8B%E8%B7%AF%E7%BA%BF" tabindex="-1">🗺️ 全程路线</h4><p><strong>南京 → 千岛湖</strong>：</p><ul><li><strong>路线</strong>：南京市区 → 宁宣高速 (S55) → 宣桐高速 (S05) → 溧宁高速 (G40) → 千岛湖出口</li><li><strong>距离</strong>：365 公里</li><li><strong>耗时</strong>：4-4.5 小时</li><li><strong>过路费</strong>：¥180</li></ul><p><strong>千岛湖 → 湍口温泉</strong>：</p><ul><li><strong>路线</strong>：千岛湖镇 → 千汾线 → 昌文线 → 湍口镇</li><li><strong>距离</strong>：70 公里</li><li><strong>耗时</strong>：1-1.5 小时</li><li><strong>过路费</strong>：免费</li></ul><p><strong>湍口温泉 → 大明山</strong>：</p><ul><li><strong>路线</strong>：湍口镇 → 昌文线 → 和白线 → 大明山景区</li><li><strong>距离</strong>：45 公里</li><li><strong>耗时</strong>：1-1.5 小时</li><li><strong>过路费</strong>：¥20</li></ul><p><strong>大明山 → 南京</strong>：</p><ul><li><strong>路线</strong>：大明山 → 杭瑞高速 (G56) → 宣桐高速 (S05) → 宁宣高速 (S55) → 南京市区</li><li><strong>距离</strong>：300 公里</li><li><strong>耗时</strong>：3-3.5 小时</li><li><strong>过路费</strong>：¥140</li></ul><h3 id="%E8%B7%AF%E7%BA%BF%E5%9B%BE%E4%B8%8E%E9%A9%BE%E9%A9%B6%E8%B4%B4%E5%A3%AB" tabindex="-1"><strong>路线图与驾驶贴士</strong></h3><ul><li><strong>整体路况</strong>：<strong>高速+省道+山路</strong> 组合，对驾驶技术有一定要求，但风景绝美。</li><li><strong>电动车专属建议</strong>：<ol><li><strong>充电规划</strong>：出发前满电。千岛湖酒店、湍口酒店均有充电桩，建议<strong>每晚住下即充电</strong>，确保次日续航无忧。</li><li><strong>能耗估算</strong>：全程约800km，按20kWh/100km能耗计算，总电费约 <strong>¥160</strong> (以1元/度电估算)。</li><li><strong>山路技巧</strong>：使用车辆的能量回收模式，下坡时可反向充电，节省能耗。</li></ol></li></ul><h3 id="%E2%9A%A1-%E7%94%B5%E5%8A%A8%E8%BD%A6%E4%B8%93%E5%B1%9E%E6%94%BB%E7%95%A5" tabindex="-1">⚡ 电动车专属攻略</h3><h4 id="%F0%9F%9A%98-%E5%B0%8F%E7%B1%B3-su7-max-%E7%94%B5%E5%8A%A8%E8%BD%A6%E6%B7%B1%E5%BA%A6%E5%88%86%E6%9E%90" tabindex="-1">🚘 小米 SU7 Max 电动车深度分析</h4><p><strong>车辆参数</strong>：</p><ul><li><strong>续航里程</strong>：550km（NEDC）</li><li><strong>充电时间</strong>：快充 30 分钟（30%-80%）</li><li><strong>充电功率</strong>：最大 100kW</li><li><strong>能耗</strong>：约 20kWh/100km</li></ul><p><strong>充电规划</strong>：</p><table><thead><tr><th>充电地点</th><th>充电时间</th><th>充电量</th><th>费用</th><th>备注</th></tr></thead><tbody><tr><td><strong>南京出发前</strong></td><td>1 小时</td><td>100%</td><td>¥50</td><td>满电出发</td></tr><tr><td><strong>宣城东服务区</strong></td><td>30 分钟</td><td>80%</td><td>¥40</td><td>中途补电</td></tr><tr><td><strong>千岛湖酒店</strong></td><td>8 小时</td><td>100%</td><td>¥50</td><td>夜间充电</td></tr><tr><td><strong>湍口温泉酒店</strong></td><td>8 小时</td><td>100%</td><td>¥50</td><td>夜间充电</td></tr><tr><td><strong>大明山景区</strong></td><td>30 分钟</td><td>80%</td><td>¥40</td><td>返程前补电</td></tr></tbody></table><p><strong>充电设施</strong>：</p><ul><li><strong>高速公路服务区</strong>：国家电网充电桩，覆盖全面</li><li><strong>酒店</strong>：千岛湖和湍口温泉酒店均有充电桩</li><li><strong>景区</strong>：大明山景区有充电桩</li><li><strong>应急</strong>：可使用手机 APP 查找附近充电桩</li></ul><hr /><h2 id="%F0%9F%92%B0-%E8%B4%B9%E7%94%A8%E9%A2%84%E7%AE%97" tabindex="-1">💰 费用预算</h2><table><thead><tr><th>类别</th><th>项目</th><th>费用 (元)</th><th>备注</th></tr></thead><tbody><tr><td><strong>住宿费</strong></td><td>voco 酒店 1 晚 + 湍口温泉套餐 1 晚</td><td>¥1375</td><td>温泉套餐含早晚餐和温泉，超值</td></tr><tr><td><strong>门票费</strong></td><td>千岛湖游船 + 月光岛 + 滑雪票 + 索道</td><td>¥780</td><td>淡季部分门票免费，滑雪选择工作日更便宜</td></tr><tr><td><strong>餐饮费</strong></td><td>3 顿正餐 + 小吃饮料</td><td>¥400</td><td>鱼头餐是大头，其他可灵活安排</td></tr><tr><td><strong>交通费</strong></td><td>过路费 ¥340 + 电费 ¥160 + 停车费 ¥10</td><td>¥510</td><td>电动车相比油车节省约 ¥400 油费</td></tr><tr><td><strong>杂费</strong></td><td>滑雪租赁、零食、纪念品等</td><td>¥300</td><td>弹性支出，丰俭由人</td></tr><tr><td><strong>总计</strong></td><td></td><td><strong>≈ ¥3365</strong></td><td>预算可控，体验丰富</td></tr></tbody></table><h3 id="%F0%9F%93%8A-%E8%AF%A6%E7%BB%86%E9%A2%84%E7%AE%97%E5%88%86%E6%9E%90" tabindex="-1">📊 详细预算分析</h3><h4 id="%F0%9F%8F%A8-%E4%BD%8F%E5%AE%BF%E8%B4%B9%E7%94%A8%EF%BC%9A1375-%E5%85%83" tabindex="-1">🏨 住宿费用：1375 元</h4><table><thead><tr><th>酒店</th><th>价格</th><th>天数</th><th>费用</th><th>备注</th></tr></thead><tbody><tr><td><strong>voco 千岛湖阳光大酒店</strong></td><td>376 元 / 晚</td><td>1 晚</td><td>376 元</td><td>湖景房</td></tr><tr><td><strong>湍口众安汇尊温泉度假村</strong></td><td>999 元 / 晚</td><td>1 晚</td><td>999 元</td><td>含温泉 + 自助早晚餐</td></tr><tr><td><strong>总计</strong></td><td>-</td><td>-</td><td>1375 元</td><td>2 人分摊，687.5 元 / 人</td></tr></tbody></table><h4 id="%F0%9F%8E%AB-%E9%97%A8%E7%A5%A8%E8%B4%B9%E7%94%A8%EF%BC%9A780-%E5%85%83" tabindex="-1">🎫 门票费用：780 元</h4><table><thead><tr><th>项目</th><th>价格</th><th>人数</th><th>费用</th><th>备注</th></tr></thead><tbody><tr><td><strong>千岛湖景区门票</strong></td><td>免费</td><td>2 人</td><td>0 元</td><td>淡季免费</td></tr><tr><td><strong>千岛湖游船票</strong></td><td>45 元 / 人</td><td>2 人</td><td>90 元</td><td>中心湖区</td></tr><tr><td><strong>梅峰岛缆车</strong></td><td>60 元 / 人</td><td>2 人</td><td>0 元</td><td>可选，未计入</td></tr><tr><td><strong>月光岛夜游</strong></td><td>130 元 / 2 人</td><td>2 人</td><td>130 元</td><td>双人套票</td></tr><tr><td><strong>大明山滑雪票</strong></td><td>250 元 / 2 人</td><td>2 人</td><td>250 元</td><td>工作日</td></tr><tr><td><strong>大明山索道</strong></td><td>320 元 / 2 人</td><td>2 人</td><td>320 元</td><td>双人来回</td></tr><tr><td><strong>教练费用</strong></td><td>300 元 / 1 小时</td><td>-</td><td>0 元</td><td>可选，未计入</td></tr><tr><td><strong>总计</strong></td><td>-</td><td>-</td><td>¥780 元</td><td>2 人分摊，390 元 / 人</td></tr></tbody></table><h4 id="%F0%9F%8D%BD%EF%B8%8F-%E9%A4%90%E9%A5%AE%E8%B4%B9%E7%94%A8%EF%BC%9A%E5%88%9D%E6%AD%A5%E9%A2%84%E8%AE%A1%EF%BC%8C%E4%BB%A5%E5%AE%9E%E9%99%85%E6%83%85%E5%86%B5%E4%B8%BA%E5%87%86" tabindex="-1">🍽️ 餐饮费用：初步预计，以实际情况为准</h4><table><thead><tr><th>项目</th><th>价格</th><th>人数</th><th>费用</th><th>备注</th></tr></thead><tbody><tr><td><strong>餐饮费</strong></td><td>400 元</td><td>2 人</td><td>400 元</td><td>灵活分配，落地再说</td></tr><tr><td><strong>总计</strong></td><td>-</td><td>-</td><td>400 元</td><td>2 人分摊，200 元 / 人</td></tr></tbody></table><h4 id="%F0%9F%9A%97-%E4%BA%A4%E9%80%9A%E8%B4%B9%E7%94%A8%EF%BC%9A510-%E5%85%83" tabindex="-1">🚗 交通费用：510 元</h4><table><thead><tr><th>项目</th><th>价格</th><th>人数</th><th>费用</th><th>备注</th></tr></thead><tbody><tr><td><strong>过路费</strong></td><td>340 元</td><td>2 人</td><td>340 元</td><td>全程</td></tr><tr><td><strong>电费</strong></td><td>160 元</td><td>2 人</td><td>160 元</td><td>小米 SU7 Max</td></tr><tr><td><strong>停车费</strong></td><td>10 元</td><td>2 人</td><td>10 元</td><td>景区停车</td></tr><tr><td><strong>总计</strong></td><td>-</td><td>-</td><td>510 元</td><td>2 人分摊，255 元 / 人</td></tr></tbody></table><h4 id="%F0%9F%9B%8D%EF%B8%8F-%E8%B4%AD%E7%89%A9%E8%B4%B9%E7%94%A8%EF%BC%9A%E5%8F%AF%E9%80%89%EF%BC%8C%E4%BB%A5%E5%AE%9E%E9%99%85%E6%83%85%E5%86%B5%E4%B8%BA%E5%87%86" tabindex="-1">🛍️ 购物费用：可选，以实际情况为准</h4><table><thead><tr><th>项目</th><th>价格</th><th>数量</th><th>费用</th><th>备注</th></tr></thead><tbody><tr><td><strong>千岛湖鱼干</strong></td><td>50 元 / 斤</td><td>2 斤</td><td>100 元</td><td>特产</td></tr><tr><td><strong>千岛玉叶茶</strong></td><td>100 元 / 斤</td><td>1 斤</td><td>100 元</td><td>茶叶</td></tr><tr><td><strong>其他纪念品</strong></td><td>-</td><td>-</td><td>-</td><td>可选，以实际情况为准</td></tr><tr><td><strong>总计</strong></td><td>-</td><td>-</td><td>-</td><td>可选，以实际情况为准</td></tr></tbody></table><h4 id="%F0%9F%92%B0-%E6%80%BB%E9%A2%84%E7%AE%97%E6%B1%87%E6%80%BB" tabindex="-1">💰 总预算汇总</h4><table><thead><tr><th>项目</th><th>费用</th><th>备注</th><th>占比</th></tr></thead><tbody><tr><td><strong>住宿费用</strong></td><td>1375 元</td><td>687.5 元 / 人</td><td>44.9%</td></tr><tr><td><strong>门票费用</strong></td><td>780 元</td><td>390 元 / 人</td><td>25.4%</td></tr><tr><td><strong>餐饮费用</strong></td><td>400 元</td><td>初步预计，以实际情况为准</td><td>13.1%</td></tr><tr><td><strong>交通费用</strong></td><td>510 元</td><td>255 元 / 人</td><td>16.6%</td></tr><tr><td><strong>购物费用</strong></td><td>0 元</td><td>可选，以实际情况为准</td><td>0%</td></tr><tr><td><strong>总计</strong></td><td><strong>3065 元</strong></td><td><strong>1532.5 元 / 人</strong></td><td><strong>100%</strong></td></tr></tbody></table><p><strong>实际预算约 3000 元，其中最多可以节省约 400 元</strong> ✅</p><hr /><h2 id="%E2%9A%A0%EF%B8%8F-%E9%87%8D%E8%A6%81%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%E4%B8%8E%E8%B4%B4%E5%BF%83%E6%8F%90%E7%A4%BA" tabindex="-1">⚠️ 重要注意事项与贴心提示</h2><h3 id="%E8%A1%8C%E5%89%8D%E5%87%86%E5%A4%87%E6%B8%85%E5%8D%95" tabindex="-1"><strong>行前准备清单</strong></h3><ul><li><strong>证件类</strong>：身份证、驾驶证、行驶证。</li><li><strong>衣物类</strong>：<strong>滑雪服/防水外套</strong>、保暖内衣、毛衣、帽子、手套、围巾、泳衣、舒适徒步鞋。</li><li><strong>电子类</strong>：手机、充电宝、<strong>车载充电器/转接头</strong>、相机。</li><li><strong>个护类</strong>：高倍防晒霜（雪地紫外线强）、润肤露、保温杯。</li><li><strong>药品类</strong>：感冒药、肠胃药、创可贴。</li></ul><h3 id="%F0%9F%8F%9E%EF%B8%8F-%E5%8D%83%E5%B2%9B%E6%B9%96%E6%B8%B8%E8%A7%88%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1">🏞️ 千岛湖游览注意事项</h3><h4 id="%E2%9B%B5-%E6%B8%B8%E8%88%B9%E5%AE%89%E5%85%A8" tabindex="-1">⛵ 游船安全</h4><ul><li>登船时注意脚下，避免滑倒</li><li>穿防滑鞋，特别是在雨天</li><li>遵守船上安全规定</li></ul><h4 id="%E2%98%80%EF%B8%8F-%E5%A4%A9%E6%B0%94%E5%87%86%E5%A4%87" tabindex="-1">☀️ 天气准备</h4><ul><li>湖面紫外线强，需做好防晒</li><li>戴帽子、太阳镜，保护头部和眼睛</li><li>涂抹防晒霜，SPF30 以上</li><li>穿长袖衣物，避免晒伤</li></ul><h3 id="%E2%99%A8%EF%B8%8F-%E6%B8%A9%E6%B3%89%E4%BD%93%E9%AA%8C%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1">♨️ 温泉体验注意事项</h3><h4 id="%F0%9F%A9%B1-%E6%B8%A9%E6%B3%89%E8%A3%85%E5%A4%87" tabindex="-1">🩱 温泉装备</h4><ul><li>泳衣、泳帽（部分温泉要求）</li><li>毛巾、拖鞋（酒店通常提供）</li><li>护肤品、保湿用品</li><li>换洗衣物</li></ul><h4 id="%F0%9F%8F%A5-%E5%81%A5%E5%BA%B7%E5%AE%89%E5%85%A8" tabindex="-1">🏥 健康安全</h4><ul><li>高血压、心脏病患者谨慎泡温泉</li><li>糖尿病患者注意血糖变化</li><li>皮肤病患者避免泡公共温泉</li><li>孕妇、哺乳期妇女避免泡温泉</li></ul><h4 id="%F0%9F%91%B6-%E5%84%BF%E7%AB%A5%E5%AE%89%E5%85%A8" tabindex="-1">👶 儿童安全</h4><ul><li>1.2 米以下儿童免费</li><li>需有成人陪同</li><li>儿童池水温适宜，适合儿童</li><li>儿童需穿泳衣、泳帽</li></ul><h3 id="%E2%9B%B7%EF%B8%8F-%E6%BB%91%E9%9B%AA%E5%AE%89%E5%85%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1">⛷️ 滑雪安全注意事项</h3><h4 id="%F0%9F%8E%BF-%E6%BB%91%E9%9B%AA%E8%A3%85%E5%A4%87" tabindex="-1">🎿 滑雪装备</h4><ul><li>滑雪服：保暖防水</li><li>滑雪裤：防水耐磨</li><li>头盔：保护头部安全</li><li>手套：保暖防滑</li><li>护目镜：防止雪盲</li></ul><h4 id="%F0%9F%8F%82-%E6%BB%91%E9%9B%AA%E5%AE%89%E5%85%A8" tabindex="-1">🏂 滑雪安全</h4><ul><li>初学者建议先在练习道练习基本动作</li><li>建议请教练指导，快速掌握技巧</li><li>不要贸然上高级道</li><li>学会正确的摔倒姿势</li></ul><h4 id="%F0%9F%9A%A8-%E5%BA%94%E6%80%A5%E5%A4%84%E7%90%86" tabindex="-1">🚨 应急处理</h4><ul><li>尽量向侧面摔倒，避免后脑着地</li><li>摔倒后不要立即起身，先观察周围情况</li><li>如需帮助，举手示意</li><li>检查身体是否受伤</li></ul><h3 id="%F0%9F%93%B8-%E6%91%84%E5%BD%B1%E6%89%93%E5%8D%A1%E7%82%B9%E6%8E%A8%E8%8D%90" tabindex="-1">📸 摄影打卡点推荐</h3><h4 id="%F0%9F%8F%9E%EF%B8%8F-%E5%8D%83%E5%B2%9B%E6%B9%96" tabindex="-1">🏞️ 千岛湖</h4><ul><li><strong>梅峰岛观景台</strong>：俯瞰 300 余座岛屿的最佳位置</li><li><strong>月光岛爱心锁</strong>：浪漫夜景拍摄点</li><li><strong>骑行绿道</strong>：动态湖景拍摄</li></ul><h4 id="%E2%99%A8%EF%B8%8F-%E6%B9%8D%E5%8F%A3%E6%B8%A9%E6%B3%89" tabindex="-1">♨️ 湍口温泉</h4><ul><li><strong>山顶无边温泉池</strong>：人景合一的绝佳拍照点</li><li><strong>巴厘岛风格门廊</strong>：异域风情拍摄</li></ul><h4 id="%E2%9B%B7%EF%B8%8F-%E5%A4%A7%E6%98%8E%E5%B1%B1" tabindex="-1">⛷️ 大明山</h4><ul><li><strong>中级雪道起点</strong>：飒爽英姿拍摄</li><li><strong>缆车上</strong>：俯瞰雪场全景</li></ul><h3 id="%F0%9F%8E%81-%E4%BC%B4%E6%89%8B%E7%A4%BC%E6%8E%A8%E8%8D%90" tabindex="-1">🎁 伴手礼推荐</h3><ul><li><strong>千岛湖鱼干</strong>：银鱼干、小鱼干，煮汤提鲜</li><li><strong>千岛玉叶茶</strong>：当地绿茶，清香爽口</li><li><strong>临安山核桃</strong>：香脆营养，休闲零食</li></ul><hr /><h2 id="%F0%9F%92%A1-%E5%AE%9E%E7%94%A8%E8%B4%B4%E5%A3%AB" tabindex="-1">💡 实用贴士</h2><h3 id="%F0%9F%93%85-%E6%9C%80%E4%BD%B3%E5%87%BA%E8%A1%8C%E6%97%B6%E9%97%B4" tabindex="-1">📅 最佳出行时间</h3><h4 id="%F0%9F%97%93%EF%B8%8F-%E5%AD%A3%E8%8A%82%E9%80%89%E6%8B%A9" tabindex="-1">🗓️ 季节选择</h4><ul><li><strong>冬季（12 月 - 2 月）</strong>：滑雪 + 温泉最佳季节，人少体验佳，推荐指数⭐⭐⭐⭐⭐</li><li><strong>春季（3 月 - 5 月）</strong>：春暖花开，景色优美，推荐指数⭐⭐⭐⭐</li><li><strong>夏季（6 月 - 8 月）</strong>：避暑胜地，水上活动丰富，推荐指数⭐⭐⭐</li><li><strong>秋季（9 月 - 11 月）</strong>：秋高气爽，景色优美，推荐指数⭐⭐⭐⭐⭐</li></ul><h4 id="%F0%9F%93%85-%E6%97%A5%E6%9C%9F%E9%80%89%E6%8B%A9" tabindex="-1">📅 日期选择</h4><ul><li><strong>工作日</strong>：游客较少，价格较低，体验更佳</li><li><strong>周末</strong>：游客较多，价格较高，需提前预订</li><li><strong>推荐</strong>：优先选择工作日出行</li></ul><h3 id="%F0%9F%93%B1-%E9%A2%84%E8%AE%A2%E6%8A%80%E5%B7%A7" tabindex="-1">📱 预订技巧</h3><h4 id="%F0%9F%8F%A8-%E9%85%92%E5%BA%97%E9%A2%84%E8%AE%A2" tabindex="-1">🏨 酒店预订</h4><ul><li>提前 1-2 周预订，价格更优惠</li><li>选择含早餐的套餐，性价比更高</li><li>关注酒店官方网站，有时会有特价活动</li><li>可选择连住优惠，价格更便宜</li></ul><h4 id="%F0%9F%8E%AB-%E9%97%A8%E7%A5%A8%E9%A2%84%E8%AE%A2" tabindex="-1">🎫 门票预订</h4><ul><li>提前在网络平台预订，比现场购买便宜</li><li>关注旅游平台，有时会有特价活动</li><li>可选择联票，比单独购买便宜</li><li>学生、老人可享受优惠票</li></ul><h3 id="%F0%9F%9B%8D%EF%B8%8F-%E8%B4%AD%E7%89%A9%E6%94%BB%E7%95%A5" tabindex="-1">🛍️ 购物攻略</h3><h4 id="%F0%9F%8E%81-%E7%89%B9%E4%BA%A7%E6%8E%A8%E8%8D%90" tabindex="-1">🎁 特产推荐</h4><ul><li><strong>千岛湖鱼干</strong>：50-100 元 / 斤，选择颜色自然、无异味的</li><li><strong>千岛玉叶茶</strong>：100-300 元 / 斤，选择干燥、香气浓郁的</li><li><strong>山核桃</strong>：80-150 元 / 斤，选择外壳完整、无虫蛀的</li></ul><h4 id="%F0%9F%9B%92-%E8%B4%AD%E7%89%A9%E5%9C%B0%E7%82%B9" tabindex="-1">🛒 购物地点</h4><ul><li><strong>千岛湖特产市场</strong>：各种当地特产齐全，价格实惠</li><li><strong>千岛湖广场</strong>：大型购物中心，品牌较多</li><li><strong>当地超市</strong>：价格实惠，种类齐全</li><li><strong>酒店礼品店</strong>：品质有保障，包装精美</li></ul><hr /><h2 id="%E2%9D%93-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98" tabindex="-1">❓ 常见问题</h2><h3 id="%F0%9F%A4%94-%E8%A1%8C%E7%A8%8B%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98" tabindex="-1">🤔 行程相关问题</h3><h4 id="%F0%9F%97%93%EF%B8%8F-%E8%A1%8C%E7%A8%8B%E5%AE%89%E6%8E%92" tabindex="-1">🗓️ 行程安排</h4><p><strong>Q: 3 天 2 晚的行程会不会太紧张？</strong><br /><strong>A:</strong> 不会，这个行程安排比较合理，每天的活动量适中，有足够的休息时间。第一天主要是交通和千岛湖游览，第二天是千岛湖深度游和温泉体验，第三天是滑雪和返程，时间安排比较宽松。</p><h4 id="%F0%9F%9A%97-%E4%BA%A4%E9%80%9A%E9%97%AE%E9%A2%98" tabindex="-1">🚗 交通问题</h4><p><strong>Q: 电动车可以完成整个行程吗？</strong><br /><strong>A:</strong> 可以，小米 SU7 Max 电动车满电续航可达 550km，完全可以完成单程。沿途有多个充电桩，包括高速公路服务区、酒店和景区，充电方便。</p><h3 id="%F0%9F%8F%A8-%E4%BD%8F%E5%AE%BF%E9%97%AE%E9%A2%98" tabindex="-1">🏨 住宿问题</h3><p><strong>Q: voco 酒店和湍口温泉酒店哪个更好？</strong><br /><strong>A:</strong> 两个酒店各有特色。voco 酒店位于千岛湖中心，湖景优美，适合欣赏湖景；湍口温泉酒店位于山区，环境幽静，温泉体验更佳。建议根据个人喜好选择。</p><h3 id="%F0%9F%8D%BD%EF%B8%8F-%E7%BE%8E%E9%A3%9F%E9%97%AE%E9%A2%98" tabindex="-1">🍽️ 美食问题</h3><p><strong>Q: 千岛湖鱼头哪家最正宗？</strong><br /><strong>A:</strong> 千岛湖鱼味馆是 30 年老字号，正宗千岛湖鱼头，价格适中，品质有保障。淳圆外寻味古村落环境优美，临湖而建，也是不错的选择。</p><h3 id="%E2%9A%A0%EF%B8%8F-%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98" tabindex="-1">⚠️ 安全问题</h3><p><strong>Q: 温泉安全吗？</strong><br /><strong>A:</strong> 温泉安全有保障，湍口温泉是天然温泉，水质优良，设施完善。只要遵守温泉规则，注意个人健康状况，完全没有问题。<br /><strong>Q: 滑雪安全吗？</strong><br /><strong>A:</strong> 滑雪安全有保障，大明山滑雪场设施完善，有专业教练指导。初学者建议先在练习道练习，不要贸然上高级道，注意安全。</p><hr /><blockquote><p><strong>✨ 旅程寄语</strong><br />三天时间，从波光粼粼的千岛湖，到雾气氤氲的温泉池，再到白雪皑皑的滑雪场，这是一场穿越水、温、冰的感官盛宴。<br />希望这份攻略能助你规划一段完美假期，在元旦之际，与重要的人，共赴山海，收藏专属你们的冬日浪漫记忆。</p></blockquote><p><strong>🚗💨 出发吧，美好的风景都在路上！</strong></p><p><em>攻略制作于 2025 年 12 月 | 祝您旅途愉快，平安归来！</em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Spring Boot Tomcat 管理控制器深度解析与实践]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/tomcat-controller" />
                <id>tag:https://fx67ll.xyz,2025-11-22:tomcat-controller</id>
                <published>2025-11-22T11:28:18+08:00</published>
                <updated>2026-03-02T15:10:48+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h2 id="%E5%89%8D%E8%A8%80" tabindex="-1">前言</h2><p>在企业级应用开发中，对服务器的监控和管理是运维工作的重要组成部分。本文将深入解析一个基于Spring Boot和若依框架的Tomcat管理控制器，探讨其设计思路、技术实现以及异常处理优化方案，并拓展相关功能的实现思路。</p><h2 id="%E4%B8%80%E3%80%81%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90%E4%B8%8E%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF" tabindex="-1">一、需求分析与设计思路</h2><h3 id="1.1-%E6%A0%B8%E5%BF%83%E9%9C%80%E6%B1%82" tabindex="-1">1.1 核心需求</h3><p>企业级应用通常需要：</p><ul><li>远程监控Tomcat运行状态</li><li>远程启动/停止Tomcat服务</li><li>完善的权限控制</li><li>详细的操作日志</li><li>健壮的异常处理机制</li></ul><h3 id="1.2-%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1" tabindex="-1">1.2 架构设计</h3><p>该控制器采用分层设计思想：</p><ul><li><strong>表现层</strong>：REST API接口，处理HTTP请求</li><li><strong>业务逻辑层</strong>：核心业务处理，包括状态查询、启动停止</li><li><strong>工具层</strong>：通用方法，如流处理、进程管理</li></ul><h2 id="%E4%BA%8C%E3%80%81%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E7%82%B9%E8%A7%A3%E6%9E%90" tabindex="-1">二、核心技术点解析</h2><h3 id="2.1-%E5%9F%BA%E7%A1%80%E6%B3%A8%E8%A7%A3%E4%B8%8E%E9%85%8D%E7%BD%AE" tabindex="-1">2.1 基础注解与配置</h3><pre><code class="language-java">@RestController@RequestMapping(&quot;/server/tomcat&quot;)public class TomcatController extends BaseController {    private static final Logger log = LoggerFactory.getLogger(TomcatController.class);        // 常量配置    private static final String TOMCAT_BIN_PATH = &quot;/usr/soft/install/apache-tomcat-9.0.7/bin&quot;;    private static final int COMMAND_TIMEOUT = 30;    private static final String TOMCAT_PROCESS_KEYWORD = &quot;apache-tomcat-9.0.7&quot;;    private static final ExecutorService STREAM_EXECUTOR = Executors.newFixedThreadPool(2);}</code></pre><p><strong>关键技术点：</strong></p><ul><li><code>@RestController</code>：标记为REST风格控制器</li><li><code>@RequestMapping</code>：定义基础URL路径</li><li>继承<code>BaseController</code>：复用若依框架基础功能</li><li>常量定义：将配置信息集中管理</li></ul><h3 id="2.2-%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6" tabindex="-1">2.2 权限控制</h3><pre><code class="language-java">@PreAuthorize(&quot;@ss.hasPermi(&#39;system:tomcat:operate&#39;)&quot;)@Log(title = &quot;Tomcat操作&quot;, businessType = BusinessType.OTHER)@PostMapping(&quot;/start&quot;)public AjaxResult startTomcat() {    // 业务逻辑}</code></pre><p><strong>权限控制实现：</strong></p><ul><li><code>@PreAuthorize</code>：基于Spring Security的方法级权限控制</li><li><code>@ss.hasPermi()</code>：若依框架的权限检查方法</li><li><code>@Log</code>：操作日志记录注解</li></ul><h3 id="2.3-%E7%8A%B6%E6%80%81%E6%9F%A5%E8%AF%A2%E5%AE%9E%E7%8E%B0" tabindex="-1">2.3 状态查询实现</h3><pre><code class="language-java">@GetMapping(&quot;/status&quot;)public AjaxResult getTomcatStatus() {    Process process = null;    BufferedReader reader = null;    try {        ProcessBuilder pb = new ProcessBuilder(&quot;ps&quot;, &quot;aux&quot;);        pb.redirectErrorStream(true);        process = pb.start();        reader = new BufferedReader(new InputStreamReader(process.getInputStream(), &quot;UTF-8&quot;));        String line;        boolean isRunning = false;        while ((line = reader.readLine()) != null) {            if (line.contains(TOMCAT_PROCESS_KEYWORD) &amp;&amp; !line.contains(&quot;grep&quot;)) {                isRunning = true;                break;            }        }        process.waitFor(5, TimeUnit.SECONDS);        String status = isRunning ? &quot;运行中&quot; : &quot;已停止&quot;;        log.info(&quot;Tomcat当前状态：{}&quot;, status);        return AjaxResult.success(&quot;查询Tomcat状态成功&quot;, status);    } catch (Exception e) {        log.error(&quot;查询Tomcat状态失败：&quot;, e);        return AjaxResult.error(&quot;查询Tomcat状态失败：&quot; + e.getMessage());    } finally {        closeReader(reader);        destroyProcess(process);    }}</code></pre><p><strong>技术要点：</strong></p><ul><li>使用<code>ProcessBuilder</code>执行系统命令</li><li>合并错误流到输入流，统一处理</li><li>读取进程输出，查找Tomcat关键字</li><li>超时控制，避免无限等待</li></ul><h2 id="%E4%B8%89%E3%80%81%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E4%BC%98%E5%8C%96%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90" tabindex="-1">三、异常处理优化深度解析</h2><h3 id="3.1-%E4%BC%A0%E7%BB%9F%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%97%AE%E9%A2%98" tabindex="-1">3.1 传统实现的问题</h3><p>在Java中执行外部进程时，常见的问题包括：</p><ul><li>流缓冲区满导致进程阻塞</li><li>资源未及时释放</li><li>缺乏超时控制</li><li>异常处理不完善</li></ul><h3 id="3.2-%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88%E4%B8%80%EF%BC%9A%E5%BC%82%E6%AD%A5%E6%B5%81%E8%AF%BB%E5%8F%96" tabindex="-1">3.2 优化方案一：异步流读取</h3><pre><code class="language-java">// 异步读取输出流（关键：避免缓冲区满导致进程阻塞）Process finalProcess = process;Future&lt;String&gt; outputFuture = STREAM_EXECUTOR.submit(() -&gt; readStream(finalProcess.getInputStream()));</code></pre><p><strong>优化原理：</strong></p><ul><li>使用线程池异步读取输入流</li><li>避免主线程阻塞在readLine()方法</li><li>防止缓冲区满导致外部进程挂起</li></ul><h3 id="3.3-%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88%E4%BA%8C%EF%BC%9A%E8%B6%85%E6%97%B6%E6%8E%A7%E5%88%B6" tabindex="-1">3.3 优化方案二：超时控制</h3><pre><code class="language-java">boolean isFinished = process.waitFor(COMMAND_TIMEOUT, TimeUnit.SECONDS);if (!isFinished) {    process.destroyForcibly(); // 超时强制销毁进程    String errorMsg = operationDesc + &quot;超时（&quot; + COMMAND_TIMEOUT + &quot;秒），已强制终止&quot;;    log.error(errorMsg);    return AjaxResult.error(errorMsg);}</code></pre><p><strong>超时处理机制：</strong></p><ul><li>设置合理的超时时间（30秒）</li><li>超时后强制销毁进程</li><li>返回明确的错误信息</li></ul><h3 id="3.4-%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88%E4%B8%89%EF%BC%9A%E8%B5%84%E6%BA%90%E9%87%8A%E6%94%BE" tabindex="-1">3.4 优化方案三：资源释放</h3><pre><code class="language-java">private void closeReader(BufferedReader reader) {    if (reader != null) {        try {            reader.close();        } catch (IOException e) {            log.error(&quot;关闭流失败&quot;, e);        }    }}private void destroyProcess(Process process) {    if (process != null &amp;&amp; process.isAlive()) {        process.destroyForcibly(); // 强制销毁避免残留        log.info(&quot;进程已强制销毁&quot;);    }}</code></pre><p><strong>资源管理优化：</strong></p><ul><li>专门的资源释放方法</li><li>检查资源状态后再释放</li><li>使用finally块确保资源释放</li></ul><h3 id="3.5-%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88%E5%9B%9B%EF%BC%9A%E5%89%8D%E7%BD%AE%E6%A3%80%E6%9F%A5" tabindex="-1">3.5 优化方案四：前置检查</h3><pre><code class="language-java">// 1. 检查Tomcat bin目录是否存在File tomcatBinDir = new File(TOMCAT_BIN_PATH);if (!tomcatBinDir.exists() || !tomcatBinDir.isDirectory()) {    String errorMsg = operationDesc + &quot;失败：Tomcat bin目录不存在或不是目录，路径：&quot; + TOMCAT_BIN_PATH;    log.error(errorMsg);    return AjaxResult.error(errorMsg);}// 2. 检查脚本文件是否存在且可读File scriptFile = new File(tomcatBinDir, scriptName);if (!scriptFile.exists() || !scriptFile.isFile()) {    String errorMsg = operationDesc + &quot;失败：脚本文件不存在，路径：&quot; + scriptFile.getAbsolutePath();    log.error(errorMsg);    return AjaxResult.error(errorMsg);}// 3. 检查脚本文件是否有读取权限if (!Files.isReadable(Paths.get(scriptFile.getAbsolutePath()))) {    String errorMsg = operationDesc + &quot;失败：没有脚本读取权限，路径：&quot; + scriptFile.getAbsolutePath();    log.error(errorMsg);    return AjaxResult.error(errorMsg);}</code></pre><p><strong>前置检查的重要性：</strong></p><ul><li>提前发现环境问题</li><li>给出明确的错误信息</li><li>避免执行无效的系统调用</li></ul><h2 id="%E5%9B%9B%E3%80%81%E6%A0%B8%E5%BF%83%E6%89%A7%E8%A1%8C%E6%96%B9%E6%B3%95%E8%A7%A3%E6%9E%90" tabindex="-1">四、核心执行方法解析</h2><h3 id="4.1-%E9%80%9A%E7%94%A8%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%96%B9%E6%B3%95" tabindex="-1">4.1 通用命令执行方法</h3><pre><code class="language-java">private AjaxResult executeCommand(String scriptName, String operationDesc) {    // 前置检查...        Process process = null;    try {        // 构建命令        ProcessBuilder pb = new ProcessBuilder(&quot;sh&quot;, scriptName);        pb.directory(tomcatBinDir);        pb.redirectErrorStream(true);        // 启动进程        log.info(&quot;开始{}，执行脚本：{}&quot;, operationDesc, scriptFile.getAbsolutePath());        process = pb.start();        // 异步读取输出        Process finalProcess = process;        Future&lt;String&gt; outputFuture = STREAM_EXECUTOR.submit(() -&gt; readStream(finalProcess.getInputStream()));        // 等待完成        boolean isFinished = process.waitFor(COMMAND_TIMEOUT, TimeUnit.SECONDS);        if (!isFinished) {            process.destroyForcibly();            return AjaxResult.error(operationDesc + &quot;超时&quot;);        }        // 获取结果        String output = outputFuture.get();        int exitCode = process.exitValue();        if (exitCode == 0) {            return AjaxResult.success(operationDesc + &quot;成功&quot;, output);        } else {            return AjaxResult.error(operationDesc + &quot;失败&quot;, output);        }    } catch (Exception e) {        // 异常处理...    } finally {        destroyProcess(process);    }}</code></pre><p><strong>设计亮点：</strong></p><ul><li>模板方法模式，复用代码</li><li>异常集中处理</li><li>结果统一封装</li></ul><h2 id="%E4%BA%94%E3%80%81%E5%8A%9F%E8%83%BD%E6%8B%93%E5%B1%95%E6%80%9D%E8%B7%AF" tabindex="-1">五、功能拓展思路</h2><h3 id="5.1-%E5%A2%9E%E5%BC%BA%E7%9B%91%E6%8E%A7%E5%8A%9F%E8%83%BD" tabindex="-1">5.1 增强监控功能</h3><pre><code class="language-java">// 查看Tomcat日志@GetMapping(&quot;/logs&quot;)public AjaxResult getTomcatLogs(@RequestParam(defaultValue = &quot;100&quot;) int lines) {    // 读取catalina.out文件最后N行}// 查看JVM状态@GetMapping(&quot;/jvm/status&quot;)public AjaxResult getJvmStatus() {    // 使用JMX获取JVM信息}// 查看应用部署情况@GetMapping(&quot;/apps&quot;)public AjaxResult getDeployedApps() {    // 读取webapps目录下的应用}</code></pre><h3 id="5.2-%E6%89%B9%E9%87%8F%E7%AE%A1%E7%90%86%E5%8A%9F%E8%83%BD" tabindex="-1">5.2 批量管理功能</h3><pre><code class="language-java">// 多实例管理@PostMapping(&quot;/batch/start&quot;)public AjaxResult batchStartTomcat(@RequestBody List&lt;String&gt; instances) {    // 批量启动多个Tomcat实例}// 集群管理@GetMapping(&quot;/cluster/status&quot;)public AjaxResult getClusterStatus() {    // 获取集群中所有节点状态}</code></pre><h3 id="5.3-%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4" tabindex="-1">5.3 自动化运维</h3><pre><code class="language-java">// 健康检查@GetMapping(&quot;/health&quot;)public AjaxResult healthCheck() {    // 综合检查Tomcat健康状态}// 自动重启@PostMapping(&quot;/auto-restart&quot;)public AjaxResult autoRestart(@RequestParam int maxRetries) {    // 失败自动重试机制}</code></pre><h3 id="5.4-%E5%AE%89%E5%85%A8%E5%A2%9E%E5%BC%BA" tabindex="-1">5.4 安全增强</h3><pre><code class="language-java">// 操作审计@PostMapping(&quot;/audit/logs&quot;)public AjaxResult getAuditLogs() {    // 查询操作审计日志}// IP白名单private boolean isAllowedIp(String ip) {    // 检查IP是否在白名单中}</code></pre><h2 id="%E5%85%AD%E3%80%81%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93" tabindex="-1">六、最佳实践总结</h2><h3 id="6.1-%E5%AE%89%E5%85%A8%E8%80%83%E8%99%91" tabindex="-1">6.1 安全考虑</h3><ol><li><strong>权限最小化</strong>：严格控制操作权限</li><li><strong>输入验证</strong>：验证所有外部输入</li><li><strong>命令安全</strong>：避免直接拼接用户输入到系统命令</li><li><strong>日志审计</strong>：记录所有关键操作</li></ol><h3 id="6.2-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96" tabindex="-1">6.2 性能优化</h3><ol><li><strong>资源池化</strong>：使用线程池管理异步任务</li><li><strong>超时控制</strong>：为所有外部调用设置超时</li><li><strong>缓存策略</strong>：缓存频繁查询的状态信息</li><li><strong>异步处理</strong>：耗时操作异步执行</li></ol><h3 id="6.3-%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F" tabindex="-1">6.3 代码质量</h3><ol><li><strong>异常处理</strong>：全面的异常捕获和处理</li><li><strong>资源管理</strong>：及时释放系统资源</li><li><strong>日志记录</strong>：详细的日志记录便于排查</li><li><strong>代码复用</strong>：提取通用方法，避免重复</li></ol><h3 id="6.4-%E8%BF%90%E7%BB%B4%E5%8F%8B%E5%A5%BD" tabindex="-1">6.4 运维友好</h3><ol><li><strong>监控告警</strong>：集成监控系统</li><li><strong>健康检查</strong>：提供健康检查接口</li><li><strong>文档完善</strong>：详细的API文档</li><li><strong>错误信息</strong>：清晰的错误提示</li></ol><h2 id="%E4%B8%83%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">七、总结</h2><p>本文深入解析了一个企业级Tomcat管理控制器的实现，重点分析了其异常处理优化方案。该控制器采用了多种先进的技术手段来确保系统的稳定性和可靠性，包括异步流读取、超时控制、资源管理等。</p><p>通过学习这个案例，我们可以掌握：</p><ul><li>Java进程管理的最佳实践</li><li>异常处理的高级技巧</li><li>企业级应用的安全考虑</li><li>系统监控的设计思路</li></ul><p>在实际项目中，我们可以基于这个基础架构，进一步拓展功能，构建更完善的服务器管理系统。</p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2025/2025-11/tomcat-controller.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[麻将室预约系统：时间段冲突与并发安全解决方案深度解析]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/mahjong-concurrent-task" />
                <id>tag:https://fx67ll.xyz,2025-11-22:mahjong-concurrent-task</id>
                <published>2025-11-22T10:21:15+08:00</published>
                <updated>2026-03-02T15:10:36+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h2 id="%E5%BC%95%E8%A8%80" tabindex="-1">引言</h2><p>在日常开发中，我们遇到了一个典型的并发场景问题：<strong>如何确保同一麻将室在同一时间段内不会被多个用户重复预约</strong>。</p><p>这个问题看似简单，但涉及到数据一致性、并发控制、业务逻辑等多个层面的考量。</p><p>本文将深入剖析这个问题的解决方案，包括多种并发控制机制的实现细节、性能对比、适用场景分析，以及在实际项目中的最佳实践，为类似系统的开发提供完整的技术参考。</p><h2 id="%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF%E5%88%86%E6%9E%90" tabindex="-1">业务场景分析</h2><h3 id="%E6%A0%B8%E5%BF%83%E4%B8%9A%E5%8A%A1%E6%B5%81%E7%A8%8B" tabindex="-1">核心业务流程</h3><p>我们的麻将室预约系统支持 Web 端和 APP 端，核心功能包括：</p><ul><li>用户可以查看麻将室列表和可用时间段</li><li>选择时间段进行预约</li><li>修改或取消已有的预约</li><li>管理员可以管理所有预约记录</li></ul><h3 id="%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1" tabindex="-1">系统架构设计</h3><pre>TypeError: Cannot read properties of undefined (reading 'v')</pre><h3 id="%E5%85%B3%E9%94%AE%E5%AE%9E%E4%BD%93%E8%AE%BE%E8%AE%A1" tabindex="-1">关键实体设计</h3><p>核心实体为<code>fx67ll_mahjong_reservation_log</code>（预约记录表），主要字段包括：</p><table><thead><tr><th>字段名</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>mahjong_reservation_log_id</code></td><td>BIGINT</td><td>预约记录 ID</td></tr><tr><td><code>user_id</code></td><td>BIGINT</td><td>用户 ID</td></tr><tr><td><code>mahjong_room_id</code></td><td>BIGINT</td><td>麻将室 ID</td></tr><tr><td><code>reservation_start_time</code></td><td>DATETIME</td><td>预约开始时间</td></tr><tr><td><code>reservation_end_time</code></td><td>DATETIME</td><td>预约结束时间</td></tr><tr><td><code>reservation_status</code></td><td>CHAR(1)</td><td>预约状态（0 - 正常，1 - 取消）</td></tr><tr><td><code>del_flag</code></td><td>CHAR(1)</td><td>删除标志（0 - 正常，2 - 删除）</td></tr><tr><td><code>create_by</code></td><td>VARCHAR(64)</td><td>创建人</td></tr><tr><td><code>create_time</code></td><td>DATETIME</td><td>创建时间</td></tr><tr><td><code>update_by</code></td><td>VARCHAR(64)</td><td>更新人</td></tr><tr><td><code>update_time</code></td><td>DATETIME</td><td>更新时间</td></tr><tr><td><code>version</code></td><td>INT</td><td>版本号（乐观锁）</td></tr></tbody></table><h2 id="%E6%A0%B8%E5%BF%83%E9%97%AE%E9%A2%98%E8%AF%86%E5%88%AB" tabindex="-1">核心问题识别</h2><p>在系统测试和实际使用过程中，我们发现了以下几个关键问题：</p><h3 id="1.-%E6%97%B6%E9%97%B4%E6%AE%B5%E9%87%8D%E5%8F%A0%E5%86%B2%E7%AA%81" tabindex="-1">1. 时间段重叠冲突</h3><p><strong>问题描述</strong>：用户可以预约与已有预约重叠的时间段，导致资源冲突。</p><p><strong>示例</strong>：</p><ul><li>已有预约：18:00-20:00</li><li>新预约：19:00-21:00</li><li>结果：两个预约时间重叠，同一麻将室无法同时容纳两批用户</li></ul><h3 id="2.-%E6%97%A0%E6%95%88%E6%97%B6%E9%97%B4%E6%95%B0%E6%8D%AE" tabindex="-1">2. 无效时间数据</h3><p><strong>问题描述</strong>：用户可以提交逻辑上无效的时间数据。</p><p><strong>示例</strong>：</p><ul><li>结束时间早于开始时间：2025-11-13 00:00:00 - 2025-11-12 03:00:00</li><li>开始时间等于结束时间：18:00:00 - 18:00:00</li></ul><h3 id="3.-%E5%B9%B6%E5%8F%91%E5%AE%89%E5%85%A8%E9%A3%8E%E9%99%A9" tabindex="-1">3. 并发安全风险</h3><p><strong>问题描述</strong>：在高并发场景下，多个用户同时预约同一时间段，可能出现 “漏判” 情况。</p><p><strong>并发问题流程图</strong>：</p><pre><code class="language-text">sequenceDiagram    participant UserA    participant UserB    participant System    participant Database        UserA-&gt;&gt;System: 查询18:00-20:00是否可用    UserB-&gt;&gt;System: 查询18:00-20:00是否可用        System-&gt;&gt;Database: SELECT * FROM reservations WHERE room=1 AND time conflicts    Database--&gt;&gt;System: 无冲突记录    System-&gt;&gt;Database: SELECT * FROM reservations WHERE room=1 AND time conflicts    Database--&gt;&gt;System: 无冲突记录        System--&gt;&gt;UserA: 时间段可用    System--&gt;&gt;UserB: 时间段可用        UserA-&gt;&gt;System: 提交预约    UserB-&gt;&gt;System: 提交预约        System-&gt;&gt;Database: INSERT INTO reservations (...)    System-&gt;&gt;Database: INSERT INTO reservations (...)        Database--&gt;&gt;System: 插入成功    Database--&gt;&gt;System: 插入成功        System--&gt;&gt;UserA: 预约成功    System--&gt;&gt;UserB: 预约成功</code></pre><h3 id="4.-%E4%B8%9A%E5%8A%A1%E5%90%88%E8%A7%84%E6%80%A7%E8%A6%81%E6%B1%82" tabindex="-1">4. 业务合规性要求</h3><p><strong>核心要求</strong>：</p><ul><li>允许相邻时间段预约（如 18:00-19:00 与 19:00-20:00）</li><li>排除已取消和已删除的记录</li><li>修改预约时需要排除自身记录</li></ul><h2 id="%E6%95%B4%E4%BD%93%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E6%B5%81%E7%A8%8B" tabindex="-1">整体解决方案流程</h2><pre>TypeError: Cannot read properties of undefined (reading 'v')</pre><h2 id="%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6%E6%9C%BA%E5%88%B6%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90" tabindex="-1">并发控制机制深度解析</h2><h3 id="1.-%E6%82%B2%E8%A7%82%E9%94%81%E6%9C%BA%E5%88%B6" tabindex="-1">1. 悲观锁机制</h3><h4 id="%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90" tabindex="-1">原理分析</h4><p>悲观锁（Pessimistic Locking）是一种并发控制策略，它假设冲突总是会发生，因此在数据操作前就对数据进行锁定，直到操作完成才释放锁。</p><p><strong>核心特点</strong>：</p><ul><li>操作前锁定，操作后释放</li><li>阻塞其他事务的并发操作</li><li>实现简单，逻辑清晰</li><li>可能导致性能问题和死锁</li></ul><h4 id="%E6%82%B2%E8%A7%82%E9%94%81%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B" tabindex="-1">悲观锁工作流程</h4><pre><code class="language-text">sequenceDiagram    participant T1 as 事务1    participant T2 as 事务2    participant DB as 数据库        T1-&gt;&gt;DB: BEGIN TRANSACTION    T2-&gt;&gt;DB: BEGIN TRANSACTION        T1-&gt;&gt;DB: SELECT * FROM reservations WHERE room=1 FOR UPDATE    DB--&gt;&gt;T1: 锁定记录，返回结果        T2-&gt;&gt;DB: SELECT * FROM reservations WHERE room=1 FOR UPDATE    note over T2,DB: T2被阻塞等待        T1-&gt;&gt;DB: INSERT INTO reservations (...)    DB--&gt;&gt;T1: 插入成功    T1-&gt;&gt;DB: COMMIT    note over DB: 释放锁        DB--&gt;&gt;T2: 返回结果（包含T1的新记录）    T2-&gt;&gt;DB: 检查冲突，发现已被预约    T2-&gt;&gt;DB: ROLLBACK</code></pre><h4 id="%E6%95%B0%E6%8D%AE%E5%BA%93%E6%82%B2%E8%A7%82%E9%94%81%E5%AE%9E%E7%8E%B0" tabindex="-1">数据库悲观锁实现</h4><p>在 MySQL 中，我们可以通过<code>SELECT ... FOR UPDATE</code>语句实现行级锁定：</p><pre><code class="language-sql">-- 悲观锁查询示例SELECT * FROM fx67ll_mahjong_reservation_log WHERE mahjong_room_id = 1 AND reservation_start_time &lt; &#39;2025-11-13 20:00:00&#39;AND reservation_end_time &gt; &#39;2025-11-13 18:00:00&#39;FOR UPDATE;</code></pre><p><strong>锁定行为分析</strong>：</p><ul><li>InnoDB 存储引擎会对查询结果集中的每行数据加行级锁</li><li>锁会一直持有到事务结束（COMMIT 或 ROLLBACK）</li><li>如果其他事务尝试修改被锁定的行，会被阻塞等待</li><li>锁定范围取决于查询条件和索引使用情况</li></ul><h4 id="%E5%AE%9E%E7%8E%B0%E4%BB%A3%E7%A0%81" tabindex="-1">实现代码</h4><pre><code class="language-java">@Servicepublic class PessimisticLockReservationService {    @Autowired    private Fx67llMahjongReservationLogMapper reservationMapper;    @Transactional(rollbackFor = Exception.class)    public void createReservation(Fx67llMahjongReservationLog log) {        // 1. 悲观锁查询冲突记录        List&lt;Fx67llMahjongReservationLog&gt; conflicts = reservationMapper.selectConflictingReservationsWithLock(            log.getMahjongRoomId(),             log.getReservationStartTime(),             log.getReservationEndTime()        );        // 2. 检查冲突        if (!conflicts.isEmpty()) {            throw new ServiceException(&quot;该时间段已被预约&quot;);        }        // 3. 创建新预约        reservationMapper.insert(log);    }}</code></pre><pre><code class="language-xml">&lt;!-- MyBatis 映射文件 --&gt;&lt;select id=&quot;selectConflictingReservationsWithLock&quot; resultMap=&quot;BaseResultMap&quot;&gt;    SELECT * FROM fx67ll_mahjong_reservation_log    WHERE mahjong_room_id = #{roomId}    AND del_flag = &#39;0&#39;    AND reservation_status != &#39;1&#39;    AND #{endTime} &gt; reservation_start_time    AND #{startTime} &lt; reservation_end_time    FOR UPDATE;&lt;/select&gt;</code></pre><h4 id="%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90" tabindex="-1">性能分析</h4><p><strong>优点</strong>：</p><ul><li>实现简单，易于理解</li><li>能够完全避免并发冲突</li><li>适合冲突率较高的场景</li></ul><p><strong>缺点</strong>：</p><ul><li>可能导致性能问题，特别是在高并发场景</li><li>可能出现死锁</li><li>长时间持有锁会影响系统吞吐量</li></ul><h3 id="2.-%E4%B9%90%E8%A7%82%E9%94%81%E6%9C%BA%E5%88%B6" tabindex="-1">2. 乐观锁机制</h3><h4 id="%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90-1" tabindex="-1">原理分析</h4><p>乐观锁（Optimistic Locking）假设冲突很少发生，因此不主动锁定数据，而是在更新时检查数据是否被其他事务修改过。</p><p><strong>核心特点</strong>：</p><ul><li>操作前不锁定，更新时检查</li><li>不阻塞其他事务的并发操作</li><li>性能较好，适合高并发场景</li><li>需要处理冲突重试逻辑</li></ul><h4 id="%E4%B9%90%E8%A7%82%E9%94%81%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B" tabindex="-1">乐观锁工作流程</h4><pre><code class="language-text">sequenceDiagram    participant T1 as 事务1    participant T2 as 事务2    participant DB as 数据库        T1-&gt;&gt;DB: BEGIN TRANSACTION    T2-&gt;&gt;DB: BEGIN TRANSACTION        T1-&gt;&gt;DB: SELECT * FROM reservations WHERE room=1    DB--&gt;&gt;T1: 返回记录（version=1）    T2-&gt;&gt;DB: SELECT * FROM reservations WHERE room=1    DB--&gt;&gt;T2: 返回记录（version=1）        T1-&gt;&gt;DB: UPDATE reservations SET ..., version=2 WHERE id=1 AND version=1    DB--&gt;&gt;T1: 更新成功（1 row affected）        T2-&gt;&gt;DB: UPDATE reservations SET ..., version=2 WHERE id=1 AND version=1    DB--&gt;&gt;T2: 更新失败（0 rows affected）        T1-&gt;&gt;DB: COMMIT    T2-&gt;&gt;DB: ROLLBACK    T2-&gt;&gt;T2: 重试逻辑或返回错误</code></pre><h4 id="%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%AF%B9%E6%AF%94" tabindex="-1">实现方式对比</h4><table><thead><tr><th>实现方式</th><th>原理</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>版本号机制</strong></td><td>添加 version 字段，更新时检查版本</td><td>实现简单，性能好</td><td>需要额外字段</td></tr><tr><td><strong>时间戳机制</strong></td><td>使用最后修改时间戳</td><td>不需要额外字段</td><td>可能存在精度问题</td></tr><tr><td><strong>字段比较机制</strong></td><td>比较所有字段的原值</td><td>不需要额外字段</td><td>实现复杂，性能差</td></tr></tbody></table><h4 id="%E7%89%88%E6%9C%AC%E5%8F%B7%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0" tabindex="-1">版本号机制实现</h4><p><strong>数据库表设计</strong>：</p><pre><code class="language-sql">ALTER TABLE fx67ll_mahjong_reservation_log ADD COLUMN version INT DEFAULT 1 NOT NULL;</code></pre><p><strong>Java 实体类</strong>：</p><pre><code class="language-java">public class Fx67llMahjongReservationLog {    // 其他字段...    private Integer version;    // getter 和 setter}</code></pre><p><strong>Service 层实现</strong>：</p><pre><code class="language-java">@Servicepublic class OptimisticLockReservationService {    @Autowired    private Fx67llMahjongReservationLogMapper reservationMapper;    @Transactional(rollbackFor = Exception.class)    public void updateReservation(Fx67llMahjongReservationLog log) {        int retryCount = 0;        final int MAX_RETRY = 3;        while (retryCount &lt; MAX_RETRY) {            try {                // 1. 查询当前记录（获取版本号）                Fx67llMahjongReservationLog current = reservationMapper.selectById(log.getId());                if (current == null) {                    throw new ServiceException(&quot;记录不存在&quot;);                }                // 2. 设置版本号（用于乐观锁检查）                log.setVersion(current.getVersion());                // 3. 检查时间段冲突                checkTimeConflict(log);                // 4. 更新记录（包含版本号检查）                int affectedRows = reservationMapper.updateWithVersion(log);                // 5. 检查更新结果                if (affectedRows &gt; 0) {                    // 更新成功，退出重试                    return;                }                // 6. 更新失败，重试                retryCount++;                if (retryCount &gt;= MAX_RETRY) {                    throw new ServiceException(&quot;更新失败，请稍后重试&quot;);                }                // 短暂延迟后重试                Thread.sleep(100);            } catch (InterruptedException e) {                Thread.currentThread().interrupt();                throw new ServiceException(&quot;操作被中断&quot;);            }        }    }    private void checkTimeConflict(Fx67llMahjongReservationLog log) {        List&lt;Fx67llMahjongReservationLog&gt; conflicts = reservationMapper.selectConflictingReservations(            log.getMahjongRoomId(),            log.getReservationStartTime(),            log.getReservationEndTime(),            log.getId() // 排除自身        );        if (!conflicts.isEmpty()) {            throw new ServiceException(&quot;该时间段已被预约&quot;);        }    }}</code></pre><pre><code class="language-xml">&lt;!-- MyBatis 更新语句 --&gt;&lt;update id=&quot;updateWithVersion&quot;&gt;    UPDATE fx67ll_mahjong_reservation_log    SET         reservation_start_time = #{reservationStartTime},        reservation_end_time = #{reservationEndTime},        version = version + 1,        update_time = NOW()    WHERE         mahjong_reservation_log_id = #{mahjongReservationLogId}        AND version = #{version}&lt;/update&gt;</code></pre><h4 id="%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90-1" tabindex="-1">性能分析</h4><p><strong>优点</strong>：</p><ul><li>高并发性能好，不会阻塞其他事务</li><li>资源利用率高</li><li>适合读多写少的场景</li></ul><p><strong>缺点</strong>：</p><ul><li>实现复杂，需要处理重试逻辑</li><li>在冲突频繁的场景下性能反而更差</li><li>可能出现活锁</li></ul><h3 id="3.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E6%9C%BA%E5%88%B6" tabindex="-1">3. 分布式锁机制</h3><h4 id="%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF" tabindex="-1">应用场景</h4><p>当系统部署在多个节点（分布式环境）时，本地数据库锁无法跨节点生效，需要使用分布式锁。</p><p><strong>分布式环境下的并发问题</strong>：</p><ul><li>多个应用节点访问同一个数据库</li><li>本地锁只能控制单个节点的并发</li><li>不同节点间的并发无法控制</li></ul><h4 id="%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E6%9E%B6%E6%9E%84%E5%9B%BE" tabindex="-1">分布式锁架构图</h4><pre>TypeError: Cannot read properties of undefined (reading 'v')</pre><h4 id="redis-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0" tabindex="-1">Redis 分布式锁实现</h4><p>基于 Redis 的 SETNX 命令实现分布式锁：</p><pre><code class="language-java">@Componentpublic class RedisDistributedLock {    @Autowired    private RedisTemplate&lt;String, String&gt; redisTemplate;    private static final String LOCK_PREFIX = &quot;reservation:lock:&quot;;    private static final int DEFAULT_EXPIRE_TIME = 30; // 30秒    public boolean tryLock(String key, String value, int expireTime) {        String lockKey = LOCK_PREFIX + key;        Boolean result = redisTemplate.opsForValue()            .setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);        return Boolean.TRUE.equals(result);    }    public void releaseLock(String key, String value) {        String lockKey = LOCK_PREFIX + key;        String currentValue = redisTemplate.opsForValue().get(lockKey);        if (value.equals(currentValue)) {            redisTemplate.delete(lockKey);        }    }}</code></pre><h4 id="redisson-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">Redisson 分布式锁</h4><p>Redisson 是一个基于 Redis 的 Java 驻内存数据网格，提供了更完善的分布式锁实现：</p><pre><code class="language-java">@Componentpublic class RedissonDistributedLockService {    @Autowired    private RedissonClient redissonClient;    public void createReservationWithDistributedLock(Fx67llMahjongReservationLog log) {        String lockKey = &quot;reservation:room:&quot; + log.getMahjongRoomId();        RLock lock = redissonClient.getLock(lockKey);        try {            // 尝试获取锁，最多等待5秒，锁自动释放时间30秒            boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);            if (!locked) {                throw new ServiceException(&quot;系统繁忙，请稍后重试&quot;);            }            // 执行业务逻辑            doCreateReservation(log);        } catch (InterruptedException e) {            Thread.currentThread().interrupt();            throw new ServiceException(&quot;操作被中断&quot;);        } finally {            // 释放锁            if (lock.isHeldByCurrentThread()) {                lock.unlock();            }        }    }    private void doCreateReservation(Fx67llMahjongReservationLog log) {        // 检查冲突和创建预约的业务逻辑        List&lt;Fx67llMahjongReservationLog&gt; conflicts = reservationMapper.selectConflictingReservations(            log.getMahjongRoomId(),            log.getReservationStartTime(),            log.getReservationEndTime()        );        if (!conflicts.isEmpty()) {            throw new ServiceException(&quot;该时间段已被预约&quot;);        }        reservationMapper.insert(log);    }}</code></pre><h4 id="zookeeper-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">Zookeeper 分布式锁</h4><p>Zookeeper 也可以用于实现分布式锁，基于其节点特性：</p><pre><code class="language-java">@Componentpublic class ZookeeperDistributedLock {    private final CuratorFramework client;    private final String lockPath;    private String currentLockPath;    public ZookeeperDistributedLock(CuratorFramework client, String lockPath) {        this.client = client;        this.lockPath = lockPath;    }    public void lock() throws Exception {        // 创建临时顺序节点        currentLockPath = client.create()            .creatingParentsIfNeeded()            .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)            .forPath(lockPath + &quot;/lock-&quot;);        // 检查是否获得锁        while (true) {            List&lt;String&gt; children = client.getChildren().forPath(lockPath);            Collections.sort(children);            if (currentLockPath.endsWith(children.get(0))) {                // 获得锁                return;            }            // 监听前一个节点            String previousNode = children.get(children.indexOf(getLockName(currentLockPath)) - 1);            CountDownLatch latch = new CountDownLatch(1);            client.getData().usingWatcher((Watcher) event -&gt; {                if (event.getType() == Watcher.Event.EventType.NodeDeleted) {                    latch.countDown();                }            }).forPath(lockPath + &quot;/&quot; + previousNode);            latch.await();        }    }    public void unlock() throws Exception {        client.delete().forPath(currentLockPath);    }    private String getLockName(String path) {        return path.substring(path.lastIndexOf(&quot;/&quot;) + 1);    }}</code></pre><h3 id="4.-zookeeper-%E7%9A%84%E7%89%B9%E6%80%A7%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%82%E5%90%88%E5%AE%9E%E7%8E%B0%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">4. Zookeeper 的特性为什么适合实现分布式锁</h3><p>ZooKeeper 作为分布式协调服务，其核心特性与分布式锁的核心需求（<strong>互斥性、安全性、可用性、公平性</strong>）高度契合，这使得它成为实现分布式锁的经典方案之一。要理解这一点，需要先拆解 ZooKeeper 的关键特性，并对应分析其如何满足分布式锁的设计要求。</p><h4 id="%E4%B8%80%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E6%A0%B8%E5%BF%83%E9%9C%80%E6%B1%82" tabindex="-1">一、分布式锁的核心需求</h4><p>在分析 ZooKeeper 特性前，需明确分布式锁必须解决的问题：</p><ol><li><strong>互斥性</strong>：同一时间只能有一个客户端持有锁，防止并发操作冲突。</li><li><strong>安全性</strong>：锁必须正确释放（即使客户端崩溃），避免“死锁”。</li><li><strong>可用性</strong>：锁服务需稳定（容忍部分节点故障），不能单点失效。</li><li><strong>公平性</strong>：客户端获取锁的顺序应符合请求顺序，避免“饥饿”。</li><li><strong>可重入性（可选）</strong>：同一客户端可多次获取同一把锁（需额外设计）。</li></ol><h4 id="%E4%BA%8C%E3%80%81zookeeper-%E6%A0%B8%E5%BF%83%E7%89%B9%E6%80%A7%E4%B8%8E%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E9%80%82%E9%85%8D%E6%80%A7" tabindex="-1">二、ZooKeeper 核心特性与分布式锁的适配性</h4><p>ZooKeeper 的数据模型、节点特性、监听机制等，恰好针对性解决了分布式锁的需求，具体对应关系如下：</p><h5 id="1.-%E6%A0%91%E5%BD%A2%E5%88%86%E5%B1%82%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B%EF%BC%9A%E5%A4%A9%E7%84%B6%E7%9A%84%E2%80%9C%E9%94%81%E8%B7%AF%E5%BE%84%E2%80%9D%E5%AE%9A%E4%B9%89" tabindex="-1">1. 树形分层数据模型：天然的“锁路径”定义</h5><p>ZooKeeper 的数据模型是<strong>分布式文件系统风格的树形结构</strong>，每个节点称为 <code>ZNode</code>，可类比为“目录/文件”，且每个 ZNode 有唯一的路径（如 <code>/locks/my-lock</code>）。</p><ul><li><strong>适配锁需求</strong>：可直接将“锁”映射为 ZooKeeper 中的一个指定 ZNode 路径（如 <code>/distributed-lock</code>），客户端通过“操作该路径下的子节点”来竞争锁，路径唯一性确保了“锁标识”的全局唯一性，避免不同锁的混淆。</li><li><strong>示例</strong>：若要实现“订单服务的库存锁”，可定义根路径为 <code>/locks/inventory-lock</code>，所有竞争该锁的客户端均在该路径下创建临时节点。</li></ul><h5 id="2.-%E4%B8%B4%E6%97%B6%E8%8A%82%E7%82%B9%EF%BC%88ephemeral-node%EF%BC%89%EF%BC%9A%E8%87%AA%E5%8A%A8%E9%87%8A%E6%94%BE%E9%94%81%EF%BC%8C%E9%81%BF%E5%85%8D%E6%AD%BB%E9%94%81" tabindex="-1">2. 临时节点（Ephemeral Node）：自动释放锁，避免死锁</h5><p>ZooKeeper 的 ZNode 分为<strong>持久节点（PERSISTENT）</strong> 和<strong>临时节点（EPHEMERAL）</strong>，核心差异在于：</p><ul><li>临时节点的生命周期与客户端会话（Session）绑定：若客户端崩溃、网络断开导致会话超时，ZooKeeper 会<strong>自动删除该临时节点</strong>。</li><li><strong>适配锁需求</strong>：将“持有锁”对应为“创建临时节点”——<ul><li>客户端成功创建临时节点 → 持有锁；</li><li>客户端崩溃/会话超时 → 临时节点自动删除 → 锁释放，从根本上避免了“客户端崩溃导致锁无法释放”的死锁问题。</li></ul></li><li>对比传统方案（如 Redis 基于过期时间释放锁）：ZooKeeper 临时节点的释放是“会话级触发”，无需依赖人工设置的过期时间（避免过期时间过短导致锁误释放，或过长导致死锁），安全性更高。</li></ul><h5 id="3.-%E9%A1%BA%E5%BA%8F%E4%B8%B4%E6%97%B6%E8%8A%82%E7%82%B9%EF%BC%88ephemeral_sequential-node%EF%BC%89%EF%BC%9A%E5%AE%9E%E7%8E%B0%E5%85%AC%E5%B9%B3%E9%94%81" tabindex="-1">3. 顺序临时节点（Ephemeral_Sequential Node）：实现公平锁</h5><p>ZooKeeper 支持创建<strong>顺序节点（SEQUENTIAL）</strong>：客户端创建顺序节点时，ZooKeeper 会自动在节点名后追加一个全局唯一的递增序号（如 <code>/locks/my-lock/lock-0000000001</code>、<code>lock-0000000002</code>）。结合“临时节点”特性，可实现<strong>公平锁</strong>：</p><ul><li>所有竞争锁的客户端，在同一锁路径（如 <code>/locks/my-lock</code>）下创建“顺序临时子节点”；</li><li>客户端创建节点后，获取该路径下所有子节点并排序，判断自己的节点是否为“序号最小的节点”：<ul><li>若是 → 成功获取锁；</li><li>若不是 → 监听“前一个序号更小的节点”（仅监听前一个，避免“惊群效应”）；</li></ul></li><li>当持有锁的客户端释放锁（节点被删除），后续监听的客户端会收到通知，重复判断自己是否为最小节点。</li><li><strong>核心优势</strong>：严格按照“节点创建顺序”分配锁，完全满足公平性需求，且避免了 Redis 公平锁实现中“轮询消耗资源”的问题。</li></ul><h5 id="4.-%E5%BC%BA%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%88cp-%E7%89%B9%E6%80%A7%EF%BC%89%EF%BC%9A%E7%A1%AE%E4%BF%9D%E9%94%81%E7%9A%84%E6%AD%A3%E7%A1%AE%E6%80%A7" tabindex="-1">4. 强一致性（CP 特性）：确保锁的正确性</h5><p>ZooKeeper 基于 <strong>ZAB 协议（ZooKeeper Atomic Broadcast）</strong> 实现了强一致性：所有客户端对 ZNode 的读写操作，最终会在所有存活节点上达成一致状态（即使部分节点故障）。</p><ul><li><strong>适配锁需求</strong>：分布式锁的核心风险是“重复加锁”（如两个客户端同时认为自己持有锁），而 ZooKeeper 的强一致性确保：<ul><li>同一时刻，只有一个客户端能创建“序号最小的临时节点”（即只有一个客户端能获取锁）；</li><li>客户端对锁状态的判断（是否持有、是否释放）基于全局一致的节点数据，不会出现“数据不一致导致的误判”。</li></ul></li><li>对比 Redis（AP 特性）：Redis 在主从切换时可能出现“数据同步延迟”，导致短暂的“锁重复分配”；而 ZooKeeper 即使有节点故障，只要集群中超过半数节点存活（如 3 节点集群存活 2 个），就能保证强一致性，锁的正确性更可靠。</li></ul><h5 id="5.-watcher-%E7%9B%91%E5%90%AC%E6%9C%BA%E5%88%B6%EF%BC%9A%E9%AB%98%E6%95%88%E7%9A%84%E9%94%81%E9%87%8A%E6%94%BE%E9%80%9A%E7%9F%A5" tabindex="-1">5. Watcher 监听机制：高效的锁释放通知</h5><p>ZooKeeper 提供 <strong>Watcher 机制</strong>：客户端可对某个 ZNode 注册“监听”，当该节点发生变化（如删除、子节点新增/删除）时，ZooKeeper 会主动向客户端发送通知。</p><ul><li><strong>适配锁需求</strong>：无需客户端“轮询”查询锁状态（如 Redis 需定时发送 <code>GET</code> 命令判断锁是否释放），而是通过“被动通知”实现高效唤醒：<ul><li>未获取到锁的客户端，仅监听“前一个节点”的删除事件；</li><li>当持有锁的节点删除（锁释放），监听客户端立即收到通知，进而尝试获取锁；</li></ul></li><li><strong>优势</strong>：减少无效网络请求，降低客户端和服务端的资源消耗，同时保证锁释放后能被快速竞争，提升锁的利用率。</li></ul><h5 id="6.-%E9%9B%86%E7%BE%A4%E9%AB%98%E5%8F%AF%E7%94%A8%EF%BC%9A%E9%81%BF%E5%85%8D%E9%94%81%E6%9C%8D%E5%8A%A1%E5%8D%95%E7%82%B9%E6%95%85%E9%9A%9C" tabindex="-1">6. 集群高可用：避免锁服务单点故障</h5><p>ZooKeeper 本身是<strong>分布式集群架构</strong>（通常部署 3/5/7 个节点），基于 ZAB 协议实现 leader-follower 模式：</p><ul><li>只有一个 leader 节点处理写请求，follower 节点同步数据并处理读请求；</li><li>若 leader 故障，follower 会自动选举新的 leader，集群继续提供服务（只要存活节点数 &gt; 集群节点数/2）；</li><li><strong>适配锁需求</strong>：分布式锁服务不能依赖单点（否则单点故障会导致整个锁服务不可用），ZooKeeper 集群的高可用特性确保：即使部分节点故障，锁服务仍能稳定运行，满足分布式系统的可用性要求。</li></ul><h4 id="%E4%B8%89%E3%80%81zookeeper-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%85%B8%E5%9E%8B%E5%AE%9E%E7%8E%B0%E6%B5%81%E7%A8%8B" tabindex="-1">三、ZooKeeper 分布式锁的典型实现流程</h4><p>结合上述特性，ZooKeeper 分布式锁的标准实现（以公平锁为例）流程可总结为：</p><ol><li><strong>创建锁根节点</strong>：提前在 ZooKeeper 中创建一个持久节点作为锁的根路径（如 <code>/distributed-locks</code>），确保所有客户端都基于该路径竞争锁。</li><li><strong>客户端竞争锁</strong>：客户端在根路径下创建“顺序临时子节点”（如 <code>/distributed-locks/lock-0000000001</code>）。</li><li><strong>判断是否持有锁</strong>：客户端获取根路径下的所有子节点，按序号排序，检查自己创建的节点是否为序号最小的节点：<ul><li>若是 → 成功获取锁，执行业务逻辑。</li><li>若不是 → 找到前一个序号更小的节点，为其注册“删除事件监听”，然后阻塞等待。</li></ul></li><li><strong>锁释放与唤醒</strong>：<ul><li>客户端执行完业务后，主动删除自己创建的临时节点（释放锁）；</li><li>若客户端崩溃，会话超时后 ZooKeeper 自动删除该节点；</li><li>前一个节点被删除后，监听它的客户端收到通知，重复步骤 3 判断是否持有锁。</li></ul></li><li><strong>业务执行完成</strong>：客户端释放锁后，流程结束。</li></ol><h4 id="%E5%9B%9B%E3%80%81%E6%80%BB%E7%BB%93%EF%BC%9Azookeeper-%E9%80%82%E5%90%88%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E6%A0%B8%E5%BF%83%E5%8E%9F%E5%9B%A0" tabindex="-1">四、总结：ZooKeeper 适合分布式锁的核心原因</h4><p>ZooKeeper 的特性与分布式锁需求形成了“一一对应”的解决方案，具体可概括为：</p><table><thead><tr><th>ZooKeeper 特性</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>强一致性（ZAB 协议）</td><td>确保锁不重复分配，保证正确性</td></tr><tr><td>Watcher 机制</td><td>高效通知锁释放，避免轮询消耗</td></tr><tr><td>集群高可用</td><td>避免锁服务单点故障，保证可用性</td></tr></tbody></table><p>正是这些特性的协同作用，使得 ZooKeeper 实现的分布式锁在<strong>安全性、公平性、可用性</strong>上都达到了较高水准，成为分布式系统中解决并发冲突的重要方案（如 Hadoop、HBase 等大数据框架均基于 ZooKeeper 实现分布式锁）。</p><h3 id="5.-%E5%90%84%E7%A7%8D%E9%94%81%E6%9C%BA%E5%88%B6%E5%AF%B9%E6%AF%94%E5%88%86%E6%9E%90" tabindex="-1">5. 各种锁机制对比分析</h3><table><thead><tr><th>特性</th><th>悲观锁</th><th>乐观锁</th><th>Redis 分布式锁</th><th>Zookeeper 分布式锁</th></tr></thead><tbody><tr><td><strong>实现复杂度</strong></td><td>简单</td><td>中等</td><td>中等</td><td>复杂</td></tr><tr><td><strong>性能</strong></td><td>低</td><td>高</td><td>高</td><td>中</td></tr><tr><td><strong>可靠性</strong></td><td>高</td><td>中</td><td>中</td><td>高</td></tr><tr><td><strong>死锁风险</strong></td><td>可能</td><td>无</td><td>低</td><td>无</td></tr><tr><td><strong>适用场景</strong></td><td>低并发、高冲突</td><td>高并发、低冲突</td><td>分布式、高并发</td><td>分布式、高可靠性</td></tr><tr><td><strong>资源消耗</strong></td><td>高</td><td>低</td><td>中</td><td>高</td></tr></tbody></table><h2 id="%E5%AE%8C%E6%95%B4%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E5%AE%9E%E7%8E%B0" tabindex="-1">完整解决方案实现</h2><h3 id="1.-mapper-%E5%B1%82%E5%AE%9E%E7%8E%B0" tabindex="-1">1. Mapper 层实现</h3><pre><code class="language-java">public interface Fx67llMahjongReservationLogMapper {    // 基础CRUD方法    int insert(Fx67llMahjongReservationLog record);    Fx67llMahjongReservationLog selectByPrimaryKey(Long id);    int updateByPrimaryKey(Fx67llMahjongReservationLog record);    // 冲突查询方法    List&lt;Fx67llMahjongReservationLog&gt; selectConflictingReservations(        @Param(&quot;roomId&quot;) Long roomId,        @Param(&quot;startTime&quot;) Date startTime,        @Param(&quot;endTime&quot;) Date endTime,        @Param(&quot;excludeId&quot;) Long excludeId);    // 悲观锁查询方法    List&lt;Fx67llMahjongReservationLog&gt; selectConflictingReservationsWithLock(        @Param(&quot;roomId&quot;) Long roomId,        @Param(&quot;startTime&quot;) Date startTime,        @Param(&quot;endTime&quot;) Date endTime);    // 乐观锁更新方法    int updateWithVersion(Fx67llMahjongReservationLog record);}</code></pre><pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt;&lt;mapper namespace=&quot;com.fx67ll.mapper.Fx67llMahjongReservationLogMapper&quot;&gt;    &lt;resultMap id=&quot;BaseResultMap&quot; type=&quot;com.fx67ll.domain.Fx67llMahjongReservationLog&quot;&gt;        &lt;id column=&quot;mahjong_reservation_log_id&quot; property=&quot;mahjongReservationLogId&quot;/&gt;        &lt;result column=&quot;user_id&quot; property=&quot;userId&quot;/&gt;        &lt;result column=&quot;mahjong_room_id&quot; property=&quot;mahjongRoomId&quot;/&gt;        &lt;result column=&quot;reservation_start_time&quot; property=&quot;reservationStartTime&quot;/&gt;        &lt;result column=&quot;reservation_end_time&quot; property=&quot;reservationEndTime&quot;/&gt;        &lt;result column=&quot;reservation_status&quot; property=&quot;reservationStatus&quot;/&gt;        &lt;result column=&quot;del_flag&quot; property=&quot;delFlag&quot;/&gt;        &lt;result column=&quot;create_by&quot; property=&quot;createBy&quot;/&gt;        &lt;result column=&quot;create_time&quot; property=&quot;createTime&quot;/&gt;        &lt;result column=&quot;update_by&quot; property=&quot;updateBy&quot;/&gt;        &lt;result column=&quot;update_time&quot; property=&quot;updateTime&quot;/&gt;        &lt;result column=&quot;version&quot; property=&quot;version&quot;/&gt;    &lt;/resultMap&gt;    &lt;!-- 普通冲突查询 --&gt;    &lt;select id=&quot;selectConflictingReservations&quot; resultMap=&quot;BaseResultMap&quot;&gt;        SELECT * FROM fx67ll_mahjong_reservation_log        WHERE mahjong_room_id = #{roomId}        AND del_flag = &#39;0&#39;        AND reservation_status != &#39;1&#39;        AND #{endTime} &gt; reservation_start_time        AND #{startTime} &lt; reservation_end_time        &lt;if test=&quot;excludeId != null&quot;&gt;            AND mahjong_reservation_log_id != #{excludeId}        &lt;/if&gt;    &lt;/select&gt;    &lt;!-- 悲观锁冲突查询 --&gt;    &lt;select id=&quot;selectConflictingReservationsWithLock&quot; resultMap=&quot;BaseResultMap&quot;&gt;        SELECT * FROM fx67ll_mahjong_reservation_log        WHERE mahjong_room_id = #{roomId}        AND del_flag = &#39;0&#39;        AND reservation_status != &#39;1&#39;        AND #{endTime} &gt; reservation_start_time        AND #{startTime} &lt; reservation_end_time        FOR UPDATE;    &lt;/select&gt;    &lt;!-- 乐观锁更新 --&gt;    &lt;update id=&quot;updateWithVersion&quot;&gt;        UPDATE fx67ll_mahjong_reservation_log        SET             reservation_start_time = #{reservationStartTime},            reservation_end_time = #{reservationEndTime},            reservation_status = #{reservationStatus},            update_by = #{updateBy},            update_time = NOW(),            version = version + 1        WHERE             mahjong_reservation_log_id = #{mahjongReservationLogId}            AND version = #{version}            AND del_flag = &#39;0&#39;    &lt;/update&gt;&lt;/mapper&gt;</code></pre><h3 id="2.-service-%E5%B1%82%E5%AE%9E%E7%8E%B0" tabindex="-1">2. Service 层实现</h3><pre><code class="language-java">@Service@Transactional(readOnly = true)public class Fx67llMahjongReservationLogServiceImpl implements IFx67llMahjongReservationLogService {    @Autowired    private Fx67llMahjongReservationLogMapper reservationMapper;    @Autowired    private RedisDistributedLock distributedLock;    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);    /**     * 创建预约（悲观锁实现）     */    @Override    @Transactional(rollbackFor = Exception.class)    public int createReservationWithPessimisticLock(Fx67llMahjongReservationLog log) {        // 1. 参数校验        validateReservationParams(log);        // 2. 悲观锁查询冲突        List&lt;Fx67llMahjongReservationLog&gt; conflicts = reservationMapper.selectConflictingReservationsWithLock(            log.getMahjongRoomId(),            log.getReservationStartTime(),            log.getReservationEndTime()        );        // 3. 检查冲突        if (!conflicts.isEmpty()) {            throw new ServiceException(&quot;时间段已被预约，请选择其他时间&quot;);        }        // 4. 设置创建信息        log.setCreateBy(SecurityUtils.getUsername());        log.setCreateTime(new Date());        log.setDelFlag(&quot;0&quot;);        log.setReservationStatus(&quot;0&quot;);        // 5. 保存预约        return reservationMapper.insert(log);    }    /**     * 创建预约（乐观锁实现）     */    @Override    @Transactional(rollbackFor = Exception.class)    public int createReservationWithOptimisticLock(Fx67llMahjongReservationLog log) {        // 1. 参数校验        validateReservationParams(log);        // 2. 检查冲突（非锁定查询）        List&lt;Fx67llMahjongReservationLog&gt; conflicts = reservationMapper.selectConflictingReservations(            log.getMahjongRoomId(),            log.getReservationStartTime(),            log.getReservationEndTime(),            null        );        if (!conflicts.isEmpty()) {            throw new ServiceException(&quot;时间段已被预约，请选择其他时间&quot;);        }        // 3. 设置创建信息        log.setCreateBy(SecurityUtils.getUsername());        log.setCreateTime(new Date());        log.setDelFlag(&quot;0&quot;);        log.setReservationStatus(&quot;0&quot;);        log.setVersion(1); // 初始版本号        // 4. 保存预约        return reservationMapper.insert(log);    }    /**     * 创建预约（分布式锁实现）     */    @Override    public int createReservationWithDistributedLock(Fx67llMahjongReservationLog log) {        String lockKey = &quot;reservation:room:&quot; + log.getMahjongRoomId();        String lockValue = UUID.randomUUID().toString();        try {            // 尝试获取分布式锁            boolean locked = distributedLock.tryLock(lockKey, lockValue, 30);            if (!locked) {                throw new ServiceException(&quot;系统繁忙，请稍后重试&quot;);            }            // 在事务中执行实际的预约创建            return transactionalCreateReservation(log);        } finally {            // 释放锁            distributedLock.releaseLock(lockKey, lockValue);        }    }    @Transactional(rollbackFor = Exception.class)    private int transactionalCreateReservation(Fx67llMahjongReservationLog log) {        // 检查冲突        List&lt;Fx67llMahjongReservationLog&gt; conflicts = reservationMapper.selectConflictingReservations(            log.getMahjongRoomId(),            log.getReservationStartTime(),            log.getReservationEndTime(),            null        );        if (!conflicts.isEmpty()) {            throw new ServiceException(&quot;时间段已被预约，请选择其他时间&quot;);        }        // 设置创建信息        log.setCreateBy(SecurityUtils.getUsername());        log.setCreateTime(new Date());        log.setDelFlag(&quot;0&quot;);        log.setReservationStatus(&quot;0&quot;);        // 保存预约        return reservationMapper.insert(log);    }    /**     * 参数校验     */    private void validateReservationParams(Fx67llMahjongReservationLog log) {        if (log.getMahjongRoomId() == null) {            throw new ServiceException(&quot;请选择麻将室&quot;);        }        if (log.getReservationStartTime() == null || log.getReservationEndTime() == null) {            throw new ServiceException(&quot;请选择预约时间段&quot;);        }        if (log.getReservationEndTime().compareTo(log.getReservationStartTime()) &lt;= 0) {            throw new ServiceException(&quot;结束时间必须晚于开始时间&quot;);        }        // 检查预约时长（30分钟到4小时）        long duration = log.getReservationEndTime().getTime() - log.getReservationStartTime().getTime();        if (duration &lt; 30 * 60 * 1000) { // 30分钟            throw new ServiceException(&quot;预约时长不能少于30分钟&quot;);        }        if (duration &gt; 4 * 60 * 60 * 1000) { // 4小时            throw new ServiceException(&quot;预约时长不能超过4小时&quot;);        }    }}</code></pre><h3 id="3.-controller-%E5%B1%82%E5%AE%9E%E7%8E%B0" tabindex="-1">3. Controller 层实现</h3><pre><code class="language-java">@RestController@RequestMapping(&quot;/fx67ll/mahjong/reservation&quot;)public class Fx67llMahjongReservationLogController extends BaseController {    @Autowired    private IFx67llMahjongReservationLogService reservationService;    /**     * 创建预约（悲观锁）     */    @PostMapping(&quot;/create/pessimistic&quot;)    public AjaxResult createWithPessimisticLock(@RequestBody Fx67llMahjongReservationLog log) {        try {            int result = reservationService.createReservationWithPessimisticLock(log);            return toAjax(result);        } catch (ServiceException e) {            return AjaxResult.error(e.getMessage());        }    }    /**     * 创建预约（乐观锁）     */    @PostMapping(&quot;/create/optimistic&quot;)    public AjaxResult createWithOptimisticLock(@RequestBody Fx67llMahjongReservationLog log) {        try {            int result = reservationService.createReservationWithOptimisticLock(log);            return toAjax(result);        } catch (ServiceException e) {            return AjaxResult.error(e.getMessage());        }    }    /**     * 创建预约（分布式锁）     */    @PostMapping(&quot;/create/distributed&quot;)    public AjaxResult createWithDistributedLock(@RequestBody Fx67llMahjongReservationLog log) {        try {            int result = reservationService.createReservationWithDistributedLock(log);            return toAjax(result);        } catch (ServiceException e) {            return AjaxResult.error(e.getMessage());        }    }}</code></pre><h2 id="%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B" tabindex="-1">异常处理流程</h2><pre>TypeError: Cannot read properties of undefined (reading 'v')</pre><h2 id="%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5" tabindex="-1">性能优化与最佳实践</h2><h3 id="1.-%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BC%98%E5%8C%96" tabindex="-1">1. 数据库优化</h3><h4 id="%E7%B4%A2%E5%BC%95%E4%BC%98%E5%8C%96" tabindex="-1">索引优化</h4><pre><code class="language-sql">-- 为常用查询条件创建索引CREATE INDEX idx_mahjong_room_id ON fx67ll_mahjong_reservation_log(mahjong_room_id);CREATE INDEX idx_reservation_time ON fx67ll_mahjong_reservation_log(reservation_start_time, reservation_end_time);CREATE INDEX idx_user_id ON fx67ll_mahjong_reservation_log(user_id);CREATE INDEX idx_status_del_flag ON fx67ll_mahjong_reservation_log(reservation_status, del_flag);</code></pre><h4 id="sql-%E4%BC%98%E5%8C%96" tabindex="-1">SQL 优化</h4><pre><code class="language-sql">-- 优化前：可能使用全表扫描SELECT * FROM fx67ll_mahjong_reservation_log WHERE mahjong_room_id = 1 AND reservation_start_time &lt; &#39;2025-11-13 20:00:00&#39;AND reservation_end_time &gt; &#39;2025-11-13 18:00:00&#39;;-- 优化后：使用覆盖索引，避免回表查询SELECT mahjong_reservation_log_id FROM fx67ll_mahjong_reservation_log WHERE mahjong_room_id = 1 AND reservation_start_time &lt; &#39;2025-11-13 20:00:00&#39;AND reservation_end_time &gt; &#39;2025-11-13 18:00:00&#39;;</code></pre><h3 id="2.-%E7%BC%93%E5%AD%98%E7%AD%96%E7%95%A5" tabindex="-1">2. 缓存策略</h3><h4 id="redis-%E7%BC%93%E5%AD%98%E5%AE%9E%E7%8E%B0" tabindex="-1">Redis 缓存实现</h4><pre><code class="language-java">@Servicepublic class CachedReservationService {    @Autowired    private RedisTemplate&lt;String, Object&gt; redisTemplate;    @Autowired    private Fx67llMahjongReservationLogMapper reservationMapper;    private static final String CACHE_KEY_PREFIX = &quot;reservation:&quot;;    private static final String AVAILABLE_TIMES_KEY = &quot;available_times:&quot;;    private static final int CACHE_TTL = 300; // 5分钟    @Cacheable(value = &quot;reservations&quot;, key = &quot;#id&quot;)    public Fx67llMahjongReservationLog getReservationById(Long id) {        return reservationMapper.selectByPrimaryKey(id);    }    @CacheEvict(value = &quot;reservations&quot;, key = &quot;#log.mahjongReservationLogId&quot;)    public void updateReservation(Fx67llMahjongReservationLog log) {        reservationMapper.updateByPrimaryKey(log);    }    public List&lt;TimeSlot&gt; getAvailableTimeSlots(Long roomId, String date) {        String cacheKey = AVAILABLE_TIMES_KEY + roomId + &quot;:&quot; + date;        // 尝试从缓存获取        List&lt;TimeSlot&gt; availableTimes = (List&lt;TimeSlot&gt;) redisTemplate.opsForValue().get(cacheKey);        if (availableTimes != null) {            return availableTimes;        }        // 缓存未命中，从数据库查询        availableTimes = calculateAvailableTimeSlots(roomId, date);        // 存入缓存        redisTemplate.opsForValue().set(cacheKey, availableTimes, CACHE_TTL, TimeUnit.SECONDS);        return availableTimes;    }    private List&lt;TimeSlot&gt; calculateAvailableTimeSlots(Long roomId, String date) {        // 计算可用时间段的业务逻辑        // ...        return new ArrayList&lt;&gt;();    }}</code></pre><h3 id="3.-%E5%BC%82%E6%AD%A5%E5%A4%84%E7%90%86" tabindex="-1">3. 异步处理</h3><h4 id="%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0" tabindex="-1">消息队列实现</h4><pre><code class="language-java">@Servicepublic class AsyncReservationService {    @Autowired    private RabbitTemplate rabbitTemplate;    @Autowired    private RedisTemplate&lt;String, String&gt; redisTemplate;    // 预约创建成功后发送通知    public void sendReservationNotification(Fx67llMahjongReservationLog log) {        try {            rabbitTemplate.convertAndSend(&quot;reservation.notifications&quot;,                 new ReservationNotificationDTO(log));        } catch (Exception e) {            // 发送失败，记录到重试队列            redisTemplate.opsForList().leftPush(&quot;reservation.notification.retry&quot;,                 JSON.toJSONString(log));        }    }    // 定时任务处理重试队列    @Scheduled(fixedRate = 60000) // 每分钟执行一次    public void processRetryNotifications() {        String logJson = redisTemplate.opsForList().rightPop(&quot;reservation.notification.retry&quot;);        while (logJson != null) {            try {                Fx67llMahjongReservationLog log = JSON.parseObject(logJson, Fx67llMahjongReservationLog.class);                rabbitTemplate.convertAndSend(&quot;reservation.notifications&quot;,                     new ReservationNotificationDTO(log));            } catch (Exception e) {                // 再次失败，重新放入队列                redisTemplate.opsForList().leftPush(&quot;reservation.notification.retry&quot;, logJson);                break;            }            logJson = redisTemplate.opsForList().rightPop(&quot;reservation.notification.retry&quot;);        }    }}</code></pre><h2 id="%E8%BF%9B%E9%98%B6%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF" tabindex="-1">进阶应用场景</h2><h3 id="1.-%E9%A2%84%E7%BA%A6%E8%B0%83%E5%BA%A6%E7%B3%BB%E7%BB%9F" tabindex="-1">1. 预约调度系统</h3><h4 id="%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90" tabindex="-1">需求分析</h4><p>在大型娱乐场所，可能需要更复杂的预约调度功能：</p><ul><li>多房间协同调度</li><li>时间段智能推荐</li><li>预约优先级管理</li></ul><h4 id="%E6%99%BA%E8%83%BD%E6%8E%A8%E8%8D%90%E6%B5%81%E7%A8%8B" tabindex="-1">智能推荐流程</h4><pre>TypeError: Cannot read properties of undefined (reading 'v')</pre><h4 id="%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%A1%88" tabindex="-1">实现方案</h4><pre><code class="language-java">@Servicepublic class AdvancedReservationScheduler {    @Autowired    private Fx67llMahjongRoomMapper roomMapper;    @Autowired    private Fx67llMahjongReservationLogMapper reservationMapper;    /**     * 智能推荐可用时间段     */    public List&lt;RecommendedTimeSlot&gt; recommendTimeSlots(Long preferredRoomId,                                                      Date preferredStartTime,                                                     Date preferredEndTime,                                                     int maxRecommendations) {        List&lt;RecommendedTimeSlot&gt; recommendations = new ArrayList&lt;&gt;();        // 1. 检查首选房间和时间        if (isRoomAvailable(preferredRoomId, preferredStartTime, preferredEndTime)) {            recommendations.add(createRecommendedSlot(preferredRoomId,                                                  preferredStartTime,                                                  preferredEndTime,                                                  100)); // 最高优先级        }        // 2. 查找同时间段其他可用房间        List&lt;Long&gt; availableRooms = findAvailableRoomsAtTime(preferredStartTime, preferredEndTime);        for (Long roomId : availableRooms) {            if (roomId.equals(preferredRoomId)) continue;            recommendations.add(createRecommendedSlot(roomId,                                                  preferredStartTime,                                                  preferredEndTime,                                                  80)); // 高优先级        }        // 3. 查找首选房间的其他可用时间段        List&lt;TimeSlot&gt; availableTimes = findAvailableTimesForRoom(preferredRoomId,                                                                preferredStartTime,                                                                preferredEndTime);        for (TimeSlot timeSlot : availableTimes) {            recommendations.add(createRecommendedSlot(preferredRoomId,                                                  timeSlot.getStartTime(),                                                  timeSlot.getEndTime(),                                                  calculateSimilarityScore(preferredStartTime,                                                                          preferredEndTime,                                                                          timeSlot.getStartTime(),                                                                          timeSlot.getEndTime())));        }        // 4. 排序并返回        return recommendations.stream()                .sorted(Comparator.comparingInt(RecommendedTimeSlot::getPriority).reversed())                .limit(maxRecommendations)                .collect(Collectors.toList());    }    private boolean isRoomAvailable(Long roomId, Date startTime, Date endTime) {        List&lt;Fx67llMahjongReservationLog&gt; conflicts = reservationMapper.selectConflictingReservations(            roomId, startTime, endTime, null);        return conflicts.isEmpty();    }    private int calculateSimilarityScore(Date preferredStart, Date preferredEnd,                                       Date actualStart, Date actualEnd) {        // 计算时间相似度分数        long preferredDuration = preferredEnd.getTime() - preferredStart.getTime();        long actualDuration = actualEnd.getTime() - actualStart.getTime();        long timeDiff = Math.abs(preferredStart.getTime() - actualStart.getTime());        long durationDiff = Math.abs(preferredDuration - actualDuration);        // 计算分数（0-79）        int timeScore = Math.max(0, 40 - (int)(timeDiff / (60 * 60 * 1000)));        int durationScore = Math.max(0, 39 - (int)(durationDiff / (30 * 60 * 1000)));        return timeScore + durationScore;    }    private RecommendedTimeSlot createRecommendedSlot(Long roomId, Date startTime,                                                   Date endTime, int priority) {        Fx67llMahjongRoom room = roomMapper.selectByPrimaryKey(roomId);        RecommendedTimeSlot slot = new RecommendedTimeSlot();        slot.setRoomId(roomId);        slot.setRoomName(room.getRoomName());        slot.setStartTime(startTime);        slot.setEndTime(endTime);        slot.setPriority(priority);        return slot;    }}</code></pre><h3 id="2.-%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E5%A4%84%E7%90%86" tabindex="-1">2. 分布式事务处理</h3><h4 id="%E9%9C%80%E6%B1%82%E5%88%86%E6%9E%90-1" tabindex="-1">需求分析</h4><p>在微服务架构中，预约系统可能需要与其他服务（如支付服务、通知服务）进行交互，需要保证分布式事务的一致性。</p><h4 id="%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E6%B5%81%E7%A8%8B" tabindex="-1">分布式事务流程</h4><pre>TypeError: Cannot read properties of undefined (reading 'v')</pre><h4 id="seata-%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E5%AE%9E%E7%8E%B0" tabindex="-1">Seata 分布式事务实现</h4><pre><code class="language-java">@Servicepublic class DistributedReservationService {    @Autowired    private Fx67llMahjongReservationLogMapper reservationMapper;    @Autowired    private PaymentFeignClient paymentClient;    @Autowired    private NotificationFeignClient notificationClient;    /**     * 创建预约（分布式事务）     */    @GlobalTransactional(name = &quot;create-reservation-transaction&quot;, rollbackFor = Exception.class)    public ReservationResult createReservationWithPayment(Fx67llMahjongReservationLog log,                                                       PaymentRequest paymentRequest) {        try {            // 1. 创建预约记录            int reservationResult = createReservation(log);            if (reservationResult &lt;= 0) {                throw new ServiceException(&quot;预约创建失败&quot;);            }            // 2. 创建支付订单            paymentRequest.setOrderId(log.getMahjongReservationLogId().toString());            paymentRequest.setAmount(calculateReservationFee(log));            PaymentResult paymentResult = paymentClient.createPayment(paymentRequest);            if (!&quot;SUCCESS&quot;.equals(paymentResult.getStatus())) {                throw new ServiceException(&quot;支付订单创建失败&quot;);            }            // 3. 发送预约确认通知            NotificationRequest notificationRequest = new NotificationRequest();            notificationRequest.setUserId(log.getUserId());            notificationRequest.setContent(&quot;预约创建成功，请完成支付&quot;);            notificationRequest.setType(&quot;RESERVATION_CONFIRM&quot;);            NotificationResult notificationResult = notificationClient.sendNotification(notificationRequest);            if (!&quot;SUCCESS&quot;.equals(notificationResult.getStatus())) {                throw new ServiceException(&quot;通知发送失败&quot;);            }            // 4. 返回结果            ReservationResult result = new ReservationResult();            result.setReservationId(log.getMahjongReservationLogId());            result.setPaymentId(paymentResult.getPaymentId());            result.setStatus(&quot;PENDING_PAYMENT&quot;);            return result;        } catch (Exception e) {            // 分布式事务会自动回滚            throw new ServiceException(&quot;预约处理失败：&quot; + e.getMessage());        }    }    private int createReservation(Fx67llMahjongReservationLog log) {        // 检查冲突和创建预约的逻辑        // ...        return reservationMapper.insert(log);    }    private BigDecimal calculateReservationFee(Fx67llMahjongReservationLog log) {        // 计算预约费用的逻辑        // ...        return new BigDecimal(&quot;100.00&quot;);    }}</code></pre><h2 id="%E5%AE%89%E5%85%A8%E6%80%A7%E8%AE%BE%E8%AE%A1" tabindex="-1">安全性设计</h2><h3 id="1.-%E8%BE%93%E5%85%A5%E9%AA%8C%E8%AF%81%E4%B8%8E%E9%98%B2%E6%8A%A4" tabindex="-1">1. 输入验证与防护</h3><pre><code class="language-java">@Componentpublic class ReservationInputValidator {    /**     * 验证预约输入参数     */    public void validateReservationInput(Fx67llMahjongReservationLog log) {        // 1. 基础参数验证        validateRequiredFields(log);        // 2. 时间参数验证        validateTimeParameters(log);        // 3. 防SQL注入验证        validateSqlInjection(log);        // 4. 防XSS验证        sanitizeXSSInputs(log);        // 5. 业务规则验证        validateBusinessRules(log);    }    private void validateRequiredFields(Fx67llMahjongReservationLog log) {        if (log.getMahjongRoomId() == null) {            throw new ServiceException(&quot;房间ID不能为空&quot;);        }        if (log.getUserId() == null) {            throw new ServiceException(&quot;用户ID不能为空&quot;);        }        if (log.getReservationStartTime() == null || log.getReservationEndTime() == null) {            throw new ServiceException(&quot;预约时间不能为空&quot;);        }    }    private void validateTimeParameters(Fx67llMahjongReservationLog log) {        Date now = new Date();        // 开始时间不能早于当前时间        if (log.getReservationStartTime().before(now)) {            throw new ServiceException(&quot;开始时间不能早于当前时间&quot;);        }        // 结束时间必须晚于开始时间        if (log.getReservationEndTime().compareTo(log.getReservationStartTime()) &lt;= 0) {            throw new ServiceException(&quot;结束时间必须晚于开始时间&quot;);        }        // 预约不能太遥远（最多提前7天）        Calendar calendar = Calendar.getInstance();        calendar.add(Calendar.DAY_OF_YEAR, 7);        if (log.getReservationStartTime().after(calendar.getTime())) {            throw new ServiceException(&quot;预约不能超过7天&quot;);        }    }}</code></pre><h3 id="2.-%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6%E7%BB%86%E5%8C%96" tabindex="-1">2. 权限控制细化</h3><pre><code class="language-java">@Componentpublic class ReservationPermissionService {    @Autowired    private SysUserMapper userMapper;    @Autowired    private SysRoleMapper roleMapper;    @Autowired    private Fx67llMahjongRoomManagerMapper roomManagerMapper;    /**     * 检查预约操作权限     */    public void checkReservationPermission(Long reservationId, String operation) {        String currentUsername = SecurityUtils.getUsername();        SysUser currentUser = userMapper.selectUserByUserName(currentUsername);        // 超级管理员拥有所有权限        if (isSuperAdmin(currentUser)) {            return;        }        // 获取预约信息        Fx67llMahjongReservationLog reservation = reservationMapper.selectByPrimaryKey(reservationId);        if (reservation == null) {            throw new ServiceException(&quot;预约记录不存在&quot;);        }        // 根据操作类型检查权限        switch (operation) {            case &quot;VIEW&quot;:                checkViewPermission(currentUser, reservation);                break;            case &quot;UPDATE&quot;:                checkUpdatePermission(currentUser, reservation);                break;            case &quot;DELETE&quot;:                checkDeletePermission(currentUser, reservation);                break;            case &quot;CANCEL&quot;:                checkCancelPermission(currentUser, reservation);                break;            default:                throw new ServiceException(&quot;未知的操作类型&quot;);        }    }}</code></pre><h2 id="%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%BF%90%E7%BB%B4" tabindex="-1">监控与运维</h2><h3 id="%E7%9B%91%E6%8E%A7%E4%BD%93%E7%B3%BB%E6%9E%B6%E6%9E%84" tabindex="-1">监控体系架构</h3><pre>TypeError: Cannot read properties of undefined (reading 'v')</pre><h3 id="%E5%85%B3%E9%94%AE%E7%9B%91%E6%8E%A7%E6%8C%87%E6%A0%87" tabindex="-1">关键监控指标</h3><pre><code class="language-java">@Componentpublic class ReservationPerformanceMonitor {    private final MeterRegistry meterRegistry;    public ReservationPerformanceMonitor(MeterRegistry meterRegistry) {        this.meterRegistry = meterRegistry;    }    public void recordReservationCreationTime(Duration duration) {        meterRegistry.timer(&quot;reservation.creation.time&quot;)            .record(duration);    }    public void incrementReservationCount() {        meterRegistry.counter(&quot;reservation.creation.count&quot;)            .increment();    }    public void incrementConflictCount() {        meterRegistry.counter(&quot;reservation.conflict.count&quot;)            .increment();    }    public void recordLockWaitTime(Duration duration) {        meterRegistry.timer(&quot;reservation.lock.wait.time&quot;)            .record(duration);    }}</code></pre><h2 id="%E6%80%BB%E7%BB%93%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5" tabindex="-1">总结与最佳实践</h2><h3 id="1.-%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B%E5%BB%BA%E8%AE%AE" tabindex="-1">1. 技术选型建议</h3><table><thead><tr><th>场景</th><th>推荐方案</th><th>理由</th></tr></thead><tbody><tr><td><strong>单体应用，低并发</strong></td><td>悲观锁</td><td>实现简单，可靠性高</td></tr><tr><td><strong>单体应用，高并发</strong></td><td>乐观锁</td><td>性能好，资源利用率高</td></tr><tr><td><strong>分布式应用</strong></td><td>分布式锁</td><td>跨节点并发控制</td></tr><tr><td><strong>微服务架构</strong></td><td>分布式锁 + 分布式事务</td><td>保证数据一致性</td></tr></tbody></table><h3 id="2.-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E8%A6%81%E7%82%B9" tabindex="-1">2. 性能优化要点</h3><h4 id="%E6%95%B0%E6%8D%AE%E5%BA%93%E5%B1%82%E9%9D%A2" tabindex="-1">数据库层面</h4><ul><li>合理设计索引，优化查询性能</li><li>使用分页查询，避免大数据集加载</li><li>定期清理过期数据</li><li>考虑读写分离</li></ul><h4 id="%E5%BA%94%E7%94%A8%E5%B1%82%E9%9D%A2" tabindex="-1">应用层面</h4><ul><li>使用缓存减少数据库访问</li><li>异步处理非实时任务</li><li>批量操作优化</li><li>合理使用连接池</li></ul><h4 id="%E6%9E%B6%E6%9E%84%E5%B1%82%E9%9D%A2" tabindex="-1">架构层面</h4><ul><li>服务拆分，职责单一</li><li>负载均衡，水平扩展</li><li>CDN 加速静态资源</li><li>降级和熔断机制</li></ul><h3 id="3.-%E5%AE%89%E5%85%A8%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5" tabindex="-1">3. 安全最佳实践</h3><ul><li><strong>输入验证</strong>：所有用户输入都要进行验证和净化</li><li><strong>权限控制</strong>：基于角色的访问控制，最小权限原则</li><li><strong>事务管理</strong>：确保数据操作的原子性和一致性</li><li><strong>日志记录</strong>：记录关键操作，便于审计和问题排查</li><li><strong>监控告警</strong>：实时监控系统状态，及时发现问题</li></ul><h3 id="4.-%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7%E8%AE%BE%E8%AE%A1" tabindex="-1">4. 可扩展性设计</h3><ul><li><strong>接口标准化</strong>：统一的 API 设计，便于扩展</li><li><strong>配置化</strong>：关键参数可配置，避免硬编码</li><li><strong>插件化</strong>：核心功能插件化，便于扩展</li><li><strong>事件驱动</strong>：基于事件的架构，降低耦合度</li></ul><h3 id="5.-%E8%BF%90%E7%BB%B4%E5%BB%BA%E8%AE%AE" tabindex="-1">5. 运维建议</h3><ul><li><strong>监控体系</strong>：建立完善的监控体系</li><li><strong>日志管理</strong>：集中式日志收集和分析</li><li><strong>备份策略</strong>：定期数据备份</li><li><strong>灾备方案</strong>：制定灾难恢复计划</li><li><strong>性能调优</strong>：持续的性能监控和调优</li></ul><h2 id="%E7%BB%93%E8%AF%AD" tabindex="-1">结语</h2><p>麻将室预约系统的并发控制问题是一个典型的分布式系统问题，涉及到数据一致性、性能、可靠性等多个方面。通过本文的深入分析和实践，我们提供了完整的解决方案，包括：</p><ol><li><strong>多种并发控制机制</strong>：悲观锁、乐观锁、分布式锁的详细实现和对比</li><li><strong>完整的代码实现</strong>：从数据访问层到业务逻辑层的完整代码</li><li><strong>性能优化策略</strong>：数据库优化、缓存策略、异步处理等</li><li><strong>进阶应用场景</strong>：预约调度、分布式事务等复杂场景</li><li><strong>安全性设计</strong>：输入验证、权限控制等安全措施</li></ol><p>希望本文能够为开发类似系统的工程师提供有价值的参考，帮助大家更好地理解和解决并发控制问题。在实际项目中，需要根据具体的业务场景和技术架构选择最合适的方案，并持续优化和改进。</p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2025/2025-11/mahjong-concurrent-task.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2025 户外烧烤备忘录]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2025-bbq-list" />
                <id>tag:https://fx67ll.xyz,2025-11-15:2025-bbq-list</id>
                <published>2025-11-15T10:11:51+08:00</published>
                <updated>2026-05-18T10:10:21+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%F0%9F%8D%A2-%E7%83%A7%E7%83%A4%E5%BF%85%E5%A4%87%E7%89%A9%E5%93%81%E6%B8%85%E5%8D%95---%E8%AE%A9%E6%AF%8F%E6%AC%A1%E7%83%A7%E7%83%A4%E9%83%BD%E4%BA%95%E4%BA%95%E6%9C%89%E6%9D%A1" tabindex="-1">🍢 烧烤必备物品清单 - 让每次烧烤都井井有条</h1><h2 id="%F0%9F%93%8B-%E6%B8%85%E5%8D%95%E8%AF%B4%E6%98%8E" tabindex="-1">📋 清单说明</h2><p>这份清单是根据多次烧烤经验整理而成，按照一次性用品和可重复使用用品进行分类，方便每次烧烤前快速核对。特别标注了一些使用心得和注意事项，让你的烧烤体验更加完美！</p><hr /><h2 id="%E2%99%BB%EF%B8%8F-%E4%B8%80%E6%AC%A1%E6%80%A7%E7%94%A8%E5%93%81" tabindex="-1">♻️ 一次性用品</h2><h3 id="%F0%9F%A7%BB-%E9%A4%90%E5%85%B7%E7%B1%BB" tabindex="-1">🧻 餐具类</h3><ul><li><strong>一次性手套</strong> 🧤 - 必备！至少准备两包，一包用于处理食材，一包用于取餐</li><li><strong>一次性筷子</strong> 🍽️ - 每人一双，建议多备几双备用</li><li><strong>一次性纸杯</strong> 🥤 - 用于喝水、饮料，建议选择厚一点的不易变形</li><li><strong>一次性烤盘</strong> 🥘 - 用于盛放烤好的食物，避免直接接触桌面</li><li><strong>一次性油刷</strong> 🧹 - 用于调料和食用油的涂抹</li></ul><h3 id="%F0%9F%A7%BD-%E6%B8%85%E6%B4%81%E7%94%A8%E5%93%81" tabindex="-1">🧽 清洁用品</h3><ul><li><strong>纯水湿巾</strong> 🧴 - 用于清洁手和餐具，温和无刺激</li><li><strong>酒精湿巾</strong> 🍶 - 用于消毒，特别是处理生肉后清洁手部</li><li><strong>餐巾纸</strong> 🧻 - 大量准备，用餐和清洁都需要</li></ul><h3 id="%F0%9F%8E%AA-%E5%9C%BA%E5%9C%B0%E5%B8%83%E7%BD%AE" tabindex="-1">🎪 场地布置</h3><ul><li><strong>一次性桌布</strong> 🧺 - 铺在桌子上，方便清洁，建议选择防水材质</li><li><strong>一次性食品袋</strong> 👜 - 用于装食材、垃圾或带回剩余物品</li></ul><h3 id="%F0%9F%97%91%EF%B8%8F-%E5%9E%83%E5%9C%BE%E5%A4%84%E7%90%86" tabindex="-1">🗑️ 垃圾处理</h3><ul><li><strong>小垃圾袋</strong> 🚮 - 临时装脏物品或带回的垃圾</li><li><strong>大垃圾袋</strong> 🗑️ - 那种搬家装被子用的大袋子，用于装大量垃圾</li></ul><hr /><h2 id="%F0%9F%94%84-%E5%8F%AF%E9%87%8D%E5%A4%8D%E4%BD%BF%E7%94%A8%E7%94%A8%E5%93%81" tabindex="-1">🔄 可重复使用用品</h2><h3 id="%F0%9F%94%A5-%E7%83%A7%E7%83%A4%E8%AE%BE%E5%A4%87" tabindex="-1">🔥 烧烤设备</h3><ul><li><strong>旧烧烤架</strong> 🔥 - 新烧烤架可以不用，旧的更有味道</li><li><strong>煤炭夹子</strong> 🖇️ - 用于夹取煤炭和调整炭火</li><li><strong>劳保手套</strong> 🧤 - 耐高温，处理炭火时保护双手</li><li><strong>打火机</strong> 🔥 - ⚠️ <strong>非常重要，每次都忘！</strong> 建议准备两个备用</li></ul><h3 id="%F0%9F%8C%A1%EF%B8%8F-%E7%87%83%E6%96%99%E7%B1%BB" tabindex="-1">🌡️ 燃料类</h3><ul><li><strong>煤炭</strong> ⛽ - 主要燃料，提供稳定高温</li><li><strong>固体酒精</strong> 🔥 - 每次20-30个，单独用完不储藏，安全第一</li><li><strong>自来水</strong> 💧 - 8升袋子装，用于灭火和清洁</li></ul><h3 id="%F0%9F%8D%B5-%E9%A5%AE%E5%93%81%E8%AE%BE%E5%A4%87" tabindex="-1">🍵 饮品设备</h3><ul><li><strong>煮茶烧烤炉</strong> 🍵 - 边烤边煮茶，享受双重乐趣</li><li><strong>茶壶</strong> 🍵 - 配套使用，保温效果好</li><li><strong>矿泉水</strong> 💧 - 饮用水和煮茶用</li><li><strong>茶包</strong> 🍵 - 根据个人口味选择喜欢的茶叶</li></ul><h3 id="%F0%9F%AA%91-%E6%A1%8C%E6%A4%85%E7%94%A8%E5%93%81" tabindex="-1">🪑 桌椅用品</h3><ul><li><strong>小板凳</strong> 🪑 - 轻便易携带，每人一个</li><li><strong>方形短腿小桌板</strong> 📦 - <strong>常用款</strong>，高度适中，放置食材和餐具</li><li><strong>方形长腿小桌板</strong> 📦 - <strong>不常用款</strong>，需要时备用</li><li><strong>月亮椅</strong> 🪑 - 2个，舒适的休息座椅</li><li><strong>地垫</strong> 🧶 - 考虑改用一次性，避免发霉问题</li></ul><h3 id="%F0%9F%A7%82-%E8%B0%83%E6%96%99%E7%B1%BB" tabindex="-1">🧂 调料类</h3><ul><li><strong>食用油</strong> 🏺 - 烧烤刷油，不用太多，现场倒到一次性纸杯里用</li><li><strong>大喜大烤肉酱</strong> 🥫 - 原味和孜然味必备，腌制肉类的首选</li><li><strong>东北撒料</strong> 🧂 - 盒马那款，味道正宗，提鲜增香</li><li><strong>辣椒粉</strong> 🌶️ - 根据口味适量添加，增加风味层次</li></ul><hr /><h2 id="%F0%9F%A5%A9-%E9%A3%9F%E6%9D%90%E6%B8%85%E5%8D%95" tabindex="-1">🥩 食材清单</h2><h3 id="%F0%9F%8D%96-%E8%82%89%E7%B1%BB" tabindex="-1">🍖 肉类</h3><ul><li><strong>猪梅花肉</strong> 🥩 - 切块腌制，肥瘦相间口感佳</li><li><strong>牛肋条</strong> 🥩 - 切块腌制，肉质鲜嫩多汁</li><li><strong>盒马腿排</strong> 🥩 - 品质保证，腌制后直接烤</li><li><strong>鸡翅</strong> 🍗 - 提前腌制，烤制时间较长但味道浓郁</li><li><strong>羊排</strong> 🐑 - 膻味适中，适合喜欢羊肉的朋友</li></ul><h3 id="%F0%9F%8D%A0-%E7%B4%A0%E9%A3%9F" tabindex="-1">🍠 素食</h3><ul><li><strong>烤红薯</strong> 🍠 - 双层锡纸包好，香甜软糯，解腻神器</li></ul><hr /><h2 id="%E2%9A%A0%EF%B8%8F-%E6%B8%A9%E9%A6%A8%E6%8F%90%E7%A4%BA" tabindex="-1">⚠️ 温馨提示</h2><h3 id="%F0%9F%9B%A1%EF%B8%8F-%E5%AE%89%E5%85%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1">🛡️ 安全注意事项</h3><ol><li><strong>固体酒精单独购买使用</strong>，不长期储存，避免安全隐患</li><li><strong>打火机务必准备</strong>，建议备用一个，避免临时找不到</li><li><strong>灭炭用水充足</strong>，确保完全熄灭后再离开</li><li><strong>劳保手套必备</strong>，处理高温物品时保护双手</li></ol><h3 id="%F0%9F%8E%AF-%E4%BD%BF%E7%94%A8%E5%BF%83%E5%BE%97" tabindex="-1">🎯 使用心得</h3><ol start="2"><li><strong>地垫考虑一次性</strong>，避免清洗和发霉问题</li><li><strong>调料适量准备</strong>，宁少勿多，不够可以临时购买</li><li><strong>食材提前腌制</strong>，至少提前2小时，让味道充分渗透</li></ol><h3 id="%F0%9F%93%A6-%E6%94%B6%E7%BA%B3%E5%BB%BA%E8%AE%AE" tabindex="-1">📦 收纳建议</h3><ol><li><strong>分类打包</strong>，一次性用品和可重复用品分开装</li><li><strong>易碎物品小心放置</strong>，避免运输过程中损坏</li><li><strong>常用物品放在易取位置</strong>，方便随时使用</li></ol><hr /><h2 id="%F0%9F%93%9D-%E6%B8%85%E5%8D%95%E6%A0%B8%E5%AF%B9%E8%A1%A8" tabindex="-1">📝 清单核对表</h2><p>每次出发前对照以下项目进行核对：</p><h3 id="%E2%9C%85-%E4%B8%80%E6%AC%A1%E6%80%A7%E7%94%A8%E5%93%81" tabindex="-1">✅ 一次性用品</h3><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 一次性手套（2包）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 一次性筷子（每人1双+备用）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 一次性纸杯（每人2个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 一次性烤盘（5-8个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 一次性桌布（2-3张）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 一次性食品袋（10个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 一次性油刷（2个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 纯水湿巾（2包）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 酒精湿巾（1包）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 餐巾纸（3-4包）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 小垃圾袋（5个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 大垃圾袋（2个）</li></ul><h3 id="%E2%9C%85-%E5%8F%AF%E9%87%8D%E5%A4%8D%E4%BD%BF%E7%94%A8%E7%94%A8%E5%93%81" tabindex="-1">✅ 可重复使用用品</h3><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 旧烧烤架</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 煤炭夹子</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 劳保手套</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 打火机（2个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 煤炭</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 固体酒精（20-30个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 自来水（8升）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 煮茶烧烤炉</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 茶壶</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 矿泉水</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 茶包</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 小板凳（每人1个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 方形短腿小桌板</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 月亮椅（2个）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 地垫（一次性）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 食用油</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 大喜大烤肉酱（原味+孜然）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 东北撒料</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 辣椒粉</li></ul><h3 id="%E2%9C%85-%E9%A3%9F%E6%9D%90" tabindex="-1">✅ 食材</h3><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 猪梅花肉（切块腌制）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 牛肋条（切块腌制）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 盒马腿排</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 鸡翅（腌制）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 羊排</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 烤红薯（锡纸包好）</li></ul><hr /><p><strong>🎉 祝每次烧烤都圆满成功，享受美食与友情的美好时光！</strong></p><hr /><p><em>最后更新时间：2025年11月14日</em></p><h4 id="%E4%B8%AA%E4%BA%BA%E5%8E%9F%E6%96%87" tabindex="-1">个人原文</h4><pre><code class="language-">根据以下我整理的烧烤物品清单，帮我生成一篇内容丰富一些的markdown博文一些小要求：1. 需要包含关于按一次性使用和可重复使用物品分类的一个说明2. 可以添加一些emoji3. 主要是为了帮助自己以后需要烧烤的时候，一眼就知道要带哪些东西一次性手套一次性筷子一次性纸杯一次性烤盘一次性桌布一次性食品袋一次性油刷  纯水湿巾酒精湿巾餐巾纸小垃圾袋（有些时候可以临时装一些脏了但是要带回家的物品）大垃圾袋（那种搬家装被子用的）食用油大喜大烤肉酱（原味和孜然必备）东北撒料（盒马的那款）辣椒粉小板凳旧烧烤架（新烧烤架可以不用）劳保手套煤炭夹子煤炭固体酒精（每次买20-30个左右即可，每次买每次单独用完，不储藏，放家里危险度较高）自来水（8升的袋子，灭炭用的）打火机（非常重要，每次都忘）煮茶烧烤炉茶壶矿泉水茶包方形短腿小桌板（常用）方向长腿小桌板（不常用）2个月亮椅地垫（之前的发霉扔了，后续考虑每次都是用一次性？）烤红薯（双层锡纸包好）猪梅花肉（切块腌制）盒马腿排、鸡翅、羊排牛肋条（切块腌制）</code></pre>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[使用若依开发一套基础CRUD业务时候的标准流程]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/ruoyi-crud" />
                <id>tag:https://fx67ll.xyz,2025-11-15:ruoyi-crud</id>
                <published>2025-11-15T09:45:03+08:00</published>
                <updated>2026-02-13T16:33:02+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>这篇博文，记录一下自己总结的快速开发流程，看到的朋友请忽略我的水文~</p><h2 id="%E5%89%8D%E8%A8%80" tabindex="-1">前言</h2><p>我也想顺便记录下自己这两年开发的一些小感触，随着AI的快速发展，导致我在接触一些工作中难以思考和负责的后端复杂业务之时，可以非常方便快速的获得大量的思路和灵感，甚至完全使用AI生成的解决方案，这对于效率和知识边界的改变是巨大的。</p><p>虽然会有一定试错成本，但是新人码农获得了巨大的机会快速成长，在我这么多年的工作经验看来，真的是时代的机遇。</p><p>未来，熟练使用AI的码农和故步自封的码农一定会在短时间内形成非常大的差距，最近一段时间，我必须强迫自己快速学习AI领域的开发和业务经验。</p><p>只有不断学习这一条道路，才有一定可能，让自己在喜爱的代码开发领域，有一席工作的机会。</p><h2 id="%E5%89%8D%E5%90%8E%E7%AB%AF%E5%85%AC%E5%85%B1%E6%A0%87%E5%87%86%E6%B5%81%E7%A8%8B" tabindex="-1">前后端公共标准流程</h2><ol><li>考虑功能点，设计表结构</li><li>使用若依管理系统同步表结构数据，点击导出代码</li><li>压缩包里直接包含了前后端最基础的CRUD代码</li></ol><h2 id="%E5%90%8E%E7%AB%AF%E5%85%AC%E5%85%B1%E4%BB%A3%E7%A0%81%E8%A1%A5%E5%85%85%E6%B5%81%E7%A8%8B" tabindex="-1">后端公共代码补充流程</h2><ol><li>导出的后台代码修改 import package 的路径，格式化代码<ul><li>PS1: 需要仔细检查包括 mapper.xml 里面也可能有 import 地址是错的</li><li>PS2: 新建文件夹一定要小心打开资源文件夹看看是否正确生成了，idea中resource下面生成mapper文件夹的.符合不会自动生成2个文件夹，但是package可以</li></ul></li><li>mapper.xml 里的列表查询 sql 添加按创建时间倒序的排序 <code>order by STR_TO_DATE(update_time, &quot;%Y-%m-%d %H:%i:%s&quot;) desc</code></li><li>service + serviceimpl 添加 ByUserId 的列表查询接口</li><li>serviceimpl 添加诸如以下的代码，fx67ll替换各自的实体类</li></ol><pre><code class="language-java">fx67ll.setUserId(SecurityUtils.getUserId());fx67ll.setCreateBy(SecurityUtils.getUsername());fx67ll.setCreateTime(DateUtils.getNowDate());fx67ll.setUpdateBy(SecurityUtils.getUsername());fx67ll.setUpdateTime(DateUtils.getNowDate());</code></pre><ol start="5"><li>controller 里的列表查询接口添加以下代码判断，非管理员不允许查询所有数据</li></ol><pre><code class="language-java">if (SecurityUtils.getUsername().equals(&quot;fx67ll&quot;)) {List&lt;Fx67ll&gt; list = fx67llService.selectFx67llList(fx67ll);return getDataTable(list);} else {List&lt;Fx67ll&gt; list = fx67llService.selectFx67llListByUserId(fx67ll);return getDataTable(list);}</code></pre><ol start="6"><li>domain 里添加前端后期查询需要的时间区间字段</li></ol><pre><code class="language-java">/** * 创建开始时间 */private String beginCreateTime;/** * 创建结束时间 */private String endCreateTime;/** * 更新开始时间 */private String beginUpdateTime;/** * 更新结束时间 */private String endUpdateTime;public String getBeginCreateTime() {return beginCreateTime;}public void setBeginCreateTime(String beginCreateTime) {this.beginCreateTime = beginCreateTime;}public String getEndCreateTime() {return endCreateTime;}public void setEndCreateTime(String endCreateTime) {this.endCreateTime = endCreateTime;}public String getBeginUpdateTime() {return beginUpdateTime;}public void setBeginUpdateTime(String beginUpdateTime) {this.beginUpdateTime = beginUpdateTime;}public String getEndUpdateTime() {return endUpdateTime;}public void setEndUpdateTime(String endUpdateTime) {this.endUpdateTime = endUpdateTime;}</code></pre><ol start="7"><li>mapper.xml 里的列表查询的where条件添加相关时间参数</li></ol><pre><code class="language-xml">&lt;if test=&quot;delFlag != null  and delFlag != &#39;&#39;&quot;&gt;and del_flag = #{delFlag}&lt;/if&gt;&lt;if test=&quot;userId != null &quot;&gt;and user_id = #{userId}&lt;/if&gt;&lt;if test=&quot;createBy != null  and createBy != &#39;&#39;&quot;&gt;and create_by = #{createBy}&lt;/if&gt;&lt;if test=&quot;beginCreateTime != null and endCreateTime != &#39;&#39;&quot;&gt;and create_time between #{beginCreateTime} and#{endCreateTime}&lt;/if&gt;&lt;if test=&quot;updateBy != null  and updateBy != &#39;&#39;&quot;&gt;and update_by = #{updateBy}&lt;/if&gt;&lt;if test=&quot;beginUpdateTime != null and endUpdateTime != &#39;&#39;&quot;&gt;and update_time between #{beginUpdateTime} and#{endUpdateTime}&lt;/if&gt;</code></pre><h2 id="%E5%89%8D%E7%AB%AF%E5%85%AC%E5%85%B1%E4%BB%A3%E7%A0%81%E8%A1%A5%E5%85%85%E6%B5%81%E7%A8%8B" tabindex="-1">前端公共代码补充流程</h2><ol><li>把api文件放到指定目录，修改导入路径 <code> &quot;@/api/fx67ll/&quot;</code></li><li>el-dialog 添加只允许点击close关闭的属性 <code>:close-on-click-modal=&quot;false&quot;</code></li><li>查询表单添加时间相关参数</li></ol><pre><code class="language-vue">&lt;el-form-item label=&quot;创建时间&quot;&gt;&lt;el-date-picker v-model=&quot;daterangeCreateTime&quot; style=&quot;width: 240px&quot; value-format=&quot;yyyy-MM-dd&quot; type=&quot;daterange&quot;  range-separator=&quot;-&quot; start-placeholder=&quot;开始日期&quot; end-placeholder=&quot;结束日期&quot; clearable&gt;&lt;/el-date-picker&gt;&lt;/el-form-item&gt;&lt;el-form-item label=&quot;更新时间&quot;&gt;&lt;el-date-picker v-model=&quot;daterangeUpdateTime&quot; style=&quot;width: 240px&quot; value-format=&quot;yyyy-MM-dd&quot; type=&quot;daterange&quot;  range-separator=&quot;-&quot; start-placeholder=&quot;开始日期&quot; end-placeholder=&quot;结束日期&quot; clearable&gt;&lt;/el-date-picker&gt;&lt;/el-form-item&gt;</code></pre><ol start="4"><li>表格里添加相关记录信息</li></ol><pre><code class="language-vue">&lt;el-table-column label=&quot;记录创建者&quot; align=&quot;center&quot; prop=&quot;createBy&quot; width=&quot;100&quot; /&gt;&lt;el-table-column label=&quot;记录创建时间&quot; align=&quot;center&quot; prop=&quot;createTime&quot; width=&quot;160&quot;&gt;&lt;template slot-scope=&quot;scope&quot;&gt;  &lt;span&gt;{{ parseTime(scope.row.createTime, &quot;{y}-{m}-{d} {h}:{i}:{s}&quot;) }}  &lt;/span&gt;&lt;/template&gt;&lt;/el-table-column&gt;&lt;el-table-column label=&quot;记录更新者&quot; align=&quot;center&quot; prop=&quot;updateBy&quot; width=&quot;100&quot; /&gt;&lt;el-table-column label=&quot;记录更新时间&quot; align=&quot;center&quot; prop=&quot;updateTime&quot; width=&quot;160&quot;&gt;&lt;template slot-scope=&quot;scope&quot;&gt;  &lt;span&gt;{{parseTime(scope.row.updateTime, &quot;{y}-{m}-{d} {h}:{i}:{s}&quot;)  }}&lt;/span&gt;&lt;/template&gt;&lt;/el-table-column&gt;</code></pre><ol start="5"><li>查询方法里添加时间相关代码</li></ol><pre><code class="language-js">// 重置时间段查询clearDateQueryParams() {  this.queryParams.beginCreateTime = null;  this.queryParams.endCreateTime = null;  this.queryParams.beginUpdateTime = null;  this.queryParams.endUpdateTime = null;}</code></pre><ol start="6"><li>getList 方法添加相关时间查询逻辑</li></ol><pre><code class="language-js">this.clearDateQueryParams();if (null != this.daterangeCreateTime &amp;&amp; &quot;&quot; != this.daterangeCreateTime) {  this.queryParams.beginCreateTime = this.daterangeCreateTime[0];  this.queryParams.endCreateTime = this.daterangeCreateTime[1];}if (null != this.daterangeUpdateTime &amp;&amp; &quot;&quot; != this.daterangeUpdateTime) {  this.queryParams.beginUpdateTime = this.daterangeUpdateTime[0];  this.queryParams.endUpdateTime = this.daterangeUpdateTime[1];}</code></pre><ol start="7"><li>添加时间查询相关参数</li></ol><pre><code class="language-js">// 创建时间范围daterangeCreateTime: [],// 更新时间范围daterangeUpdateTime: [],// 查询参数queryParams: {// 原来的参数基础上追加beginCreateTime: null,endCreateTime: null,beginUpdateTime: null,endUpdateTime: null,}</code></pre><ol start="8"><li>getList 方法添加处置表格空值的公共方法 <code>this.formatObjectArrayNullProperty(response.rows);</code></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2025/2025-11/dev-workflow.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2025 国庆低预算自驾游计划路书]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/national-day-self-driving-plan" />
                <id>tag:https://fx67ll.xyz,2025-09-19:national-day-self-driving-plan</id>
                <published>2025-09-19T23:44:10+08:00</published>
                <updated>2025-11-24T16:11:30+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="2025-%E5%9B%BD%E5%BA%86%E4%BD%8E%E9%A2%84%E7%AE%97%E8%87%AA%E9%A9%BE%E6%B8%B8%E8%AE%A1%E5%88%92%E8%B7%AF%E4%B9%A6" tabindex="-1">2025 国庆低预算自驾游计划路书</h1><h1 id="%F0%9F%98%82-%E5%89%8D%E6%83%85%E6%8F%90%E8%A6%81" tabindex="-1">😂 前情提要</h1><p>去年国庆女朋友就说想要出去转转，我借口说油车费用太高加上人多还是算了，等明年提电车了开电车出去转转。</p><p>于是等到今年国庆，哈哈实在躲不掉了，就用AI调查了一下国庆期间有没有低预算自驾随便转转的地方。结果，一不小心生成了一份内容丰富多彩的自驾旅游计划路书，正好就水一篇博文出来吧。</p><p><strong>PS: 不得不说，一年光景，AI已经在飞速进步发展，如果个人的学习进度再跟不上AI，也许自己也要光速下岗了。</strong></p><p>其中一个非常牛的AI直接生成了一个丰富多彩的网页（经过了很多轮的修改），不过细节上很难用提示词反复训练处理优化，所以作为一个coder最终还是自己上手改了一些源码。可以访问这个地址查看：<a href="https://ai.fx67ll.com/national-day-self-driving-plan/" target="_blank">https://ai.fx67ll.com/national-day-self-driving-plan/</a></p><p>整体费用预计来看，在个人的心理接受范围之内，一年也就出去了这么一次，就不过多纠结了~</p><h2 id="%F0%9F%94%A5-%E9%99%84%E5%BD%95%EF%BC%9Aai%E6%8F%90%E7%A4%BA%E8%AF%8D" tabindex="-1">🔥 附录：AI提示词</h2><h3 id="%E4%B8%AA%E4%BA%BA%E5%88%9D%E5%A7%8B%E9%9C%80%E6%B1%82%E7%89%88%E6%9C%AC" tabindex="-1">个人初始需求版本</h3><pre><code class="language-">南京出发，帮我规划一个国庆小众的周边游计划，最好可以躲避历史上经常堵车的路段，路上随性的开到哪玩哪里也行。预算非常紧张，最好要非常节省开销，把钱花在刀刃上。计划使用4-5天出行最好。我初步意向目的地有这些：宁波，舟山，台州，桐庐，安吉，千岛湖，黄山，看看这些地方能不能帮我串联起来做一个国庆出游计划，可以按你觉得合理的时间路线安排自行组合，尽量提供3种或以上的计划组合。可以2号出发，6号回来，要避免遇到所有历史上经常拥堵的路段。从历史来看，国庆节宁波，舟山是不是会涌入很多人？如果人多就剔除旅行计划也没问题。台州算不算热门旅游目的地？是否需要提前预定酒店，自驾的话酒店是不是可以比较随意选择，到时候开到哪再去哪里订是不是也合适？增加更为详细的景点介绍，并继续新增一些备选的景点。不要添加大奇山国家森林公园之类的爬山项目的游玩，不过可以开车走山路观光。增加更为详细的特色必吃饭店的介绍，丰富计划的可选择性。对于路上线路的选择也进行优化，增加一些备选的路线。对于费用预估，我开的是小米su7max。尽量不考虑皖南文化线的相关内容，海岛之旅能不能再穿插一下台州的行程。不需要介绍电车的好处，只需要考虑电车的费用即可。对于住宿也可以提供一些比较好的推荐。我对你的攻略还是比较满意的，但是我比较好奇岱山岛和秀山岛在历史是不是热门旅游地，会直接导致长时间堵在路上？能否再增加一些景点和游玩活动作为计划的备选地点。增加一些特色打卡的美食备选，多搜索一些小红书或者大众点评上重点推荐的，每个目的地至少列举十个必打卡或必吃食物。住宿也需要多一些介绍，最好加上图例说明每个推荐的住宿地图位置。能否丰富网站上的元素，加上真实地图的路线图？</code></pre><h3 id="deepseek%E6%8F%90%E7%82%BC%E6%80%BB%E7%BB%93%2B%E4%B8%AA%E4%BA%BA%E4%BC%98%E5%8C%96%E4%BF%AE%E6%94%B9%E8%A1%A5%E5%85%85%E7%89%88%E6%9C%AC" tabindex="-1">Deepseek提炼总结+个人优化修改补充版本</h3><pre><code class="language-">南京出发，帮我规划一个国庆小众的周边游计划，可以多搜索一些小红书和大众点评的内容。结合之前的对话，请你多分析分析我的偏好，这将是我最终成行的计划，请尽可能帮我详细的规划一下。已经确定的大致路线：• 1号或者2号出发（如果1号出发可以在途中再找个地方玩一天），避开拥堵慢慢开到宁波，帮我找一个离上舟山岛比较方便的区域住一晚• 3号早起出发，去岱山，已经预定了岱山秀山秀水湾休闲度假村私人海景公寓，玩2天• 5号继续出发，去舟山，已经预定了九千七·群岛晚霞海景美宿(朱家尖大青山店)，继续玩1天• 6号开始返回南京，路上如果从历史来看会很拥堵，则中途可以路过哪里随便逛逛核心诉求：1. 南京出发，4-5天，国庆假期2. 极致低预算，减少任何不必要的开销  3. 小众目的地，冷门景点，避开人潮4. 规避历史拥堵路段，反拥堵路线规划5. 随性自驾，开到哪玩哪，弹性安排6. 电车费用预估（小米SU7 Max），无需电车优势说明，只需能耗费用7. 非爬山项目，可驾车观光的山路目的地池：核心圈：宁波（非市区，主要是中转站，考虑离第二天上舟山比较近的区域，比如宁波镇海区），岱山，舟山（非市区，主要是岱山县/大青山那块）纳入规划的高分景点：• 中国海岬公园• 东沙古镇• 磨心山景区（傍晚可以上磨心山，在日落时分俯瞰岱山全景）• 岱山最美公路燕黄线• 岱山灯塔博物馆• 牛轭山观海公园 • 秀山岛，参观蓝白小镇（备选，据说比较一般？）• 大青山国家公园• 大青山滨海环线公路• 舟山螺塘线沿海公路避堵重点：• 规避路段：G36宁洛高速（南京段）、G25长深高速（江苏段）、G15沈海高速（宁波-舟山段）、舟山跨海大桥、杭州湾跨海大桥等历史国庆拥堵点。• 出行时间：建议10月1日或2日一早（5-6点）或中午后出发，错开出行最高峰。返程建议6号下午或者之后。• 路线选择：多利用国省道（G104, G205, S209等）、县道、乡道进行衔接，高速选择车流较少的线路（如溧芜高速、申嘉湖高速等）。• 出发避堵路线：建议早6点前出发，选择G42沪蓉高速→G4012溧宁高速→S26诸永高速→G1522常台高速→G1504宁波绕城。这条线路比常规的G25长深高速和G15沈海高速车流量小，虽然距离稍远，但通常更畅通。• 回程避堵路线：建议中午后出发，选择G9211甬舟高速→G1504宁波绕城→G92杭州湾环线→S12申嘉湖高速→G25长深高速→G42沪蓉高速。这条线路比常规的G15沈海高速车流量小，尤其S12申嘉湖高速通常较为畅通。活动与美食：1. 活动关键词：沿海公路自驾、风车公路、海岛环岛、渔村探访、精品咖啡馆、地质公园观光、夜市觅食，注明每个景点的收费和免费情况。2. 美食强化：必吃菜、老字号、本地人推荐、大排档、小吃店、网红打卡。每个目的地需提供10个以上备选。3. 已经发现的一些餐馆（酌情参考，如果你觉得不好也可以去掉，仅供参考）：岱山县高家庄海鲜楼、岱山县岱东农贸市场、朱家尖阿勇海鲜排档、朱家尖鲜多多海鲜馆、朱家尖海边咖啡馆、朱家尖小蜜蜂海鲜面馆、住宿与预订：• 3号-5号每晚的住宿已经基本确认，需要帮忙找一下宁波的歇脚点，要离上舟山岛的位置近一些，6号开始及之后开始回程，路上可以顺路逛逛输出要求：1. 包含详细景点介绍、备选景点，至少十个。2. 包含详细美食清单（店名+招牌菜），每个目的地至少十个。3. 包含住宿推荐（名称+特点+位置示意），每个目的地至少十个。4. 包含费用预估（电费+住宿费+餐饮费预估），目前一直费用3号-5号四天住宿1596（其他日程的住宿费暂时按300一天来计算，从宁波上岛的时候可能考虑洗浴中心过渡一下，然后凌晨趁车流量小的时候上岛）。5. 路线需在地图上清晰可视（可用文字描述关键节点）。6. 计划要尽可能详实，落实细节，每个方面都尽可能多规划一些备选的选择，这样可以让我实际操作有更多的预案7. 网页中的图片不要丢失，尽量多搜索图片，搜索不到需要考虑比较好的占位设计文字代替。</code></pre><hr /><p>以下就是AI生成的国庆低预算自驾游计划路书的Markdown内容了~</p><h1 id="%F0%9F%8F%96%EF%B8%8F-%E5%8D%97%E4%BA%AC-%E5%AE%81%E6%B3%A2-%E5%B2%B1%E5%B1%B1-%E8%88%9F%E5%B1%B1%E5%9B%BD%E5%BA%86%E4%BD%8E%E9%A2%84%E7%AE%97%E8%87%AA%E9%A9%BE%E6%B8%B8%E8%AE%A1%E5%88%92%E8%B7%AF%E4%B9%A6" tabindex="-1">🏖️ 南京-宁波-岱山-舟山国庆低预算自驾游计划路书</h1><p>基于你的需求，我为你规划了一份详尽的国庆小众自驾游计划。作为AI助手，我会结合可靠的搜索结果和你的偏好，提供一份充满细节和备选方案的行程，助你避开人潮，享受一个随性自在的海岛假期。</p><h2 id="%E2%9C%A8-%E8%A1%8C%E7%A8%8B%E4%BA%AE%E7%82%B9" tabindex="-1">✨ 行程亮点</h2><ul><li><strong>小众秘境探索</strong>：精选舟山群岛中开发程度较低、游客较少的岱山岛和朱家尖大青山区域，避开普陀山等热门景点，体验原生态海岛风情和宁静海滩。</li><li><strong>反拥堵智慧出行</strong>：通过历史交通数据分析，提供最佳出发时间和替代路线，有效避开G15沈海高速、杭州湾跨海大桥等传统拥堵路段，最大化利用假期时间。</li><li><strong>电车友好型路线</strong>：全程规划充电便利的路线和住宿，针对小米SU7 Max的续航能力合理安排充电路线，免除里程焦虑。</li></ul><h2 id="%F0%9F%93%8D-%E8%A1%8C%E7%A8%8B%E6%A6%82%E8%A7%88" tabindex="-1">📍 行程概览</h2><table><thead><tr><th style="text-align:left">日期</th><th style="text-align:left">行程安排</th><th style="text-align:left">主要活动内容</th><th style="text-align:left">住宿地点</th><th style="text-align:left">驾驶距离</th></tr></thead><tbody><tr><td style="text-align:left">10月1日/2日</td><td style="text-align:left">南京→宁波镇海区</td><td style="text-align:left">避开早高峰中午出发，傍晚抵达宁波</td><td style="text-align:left">宁波镇海区酒店</td><td style="text-align:left">约440km</td></tr><tr><td style="text-align:left">10月3日</td><td style="text-align:left">宁波→秀山岛</td><td style="text-align:left">上午出发秀山，一路上看看跨海大桥</td><td style="text-align:left">秀山秀水湾私人海景公寓</td><td style="text-align:left">约120km</td></tr><tr><td style="text-align:left">10月4日</td><td style="text-align:left">岱山岛全日游</td><td style="text-align:left">磨心山日落、东沙古镇、燕黄线自驾</td><td style="text-align:left">秀山秀水湾私人海景公寓</td><td style="text-align:left">环岛约60km</td></tr><tr><td style="text-align:left">10月5日</td><td style="text-align:left">秀山岛→朱家尖</td><td style="text-align:left">上午出发朱家尖，下午大青山环线</td><td style="text-align:left">九千七·群岛晚霞海景美宿</td><td style="text-align:left">约100km</td></tr><tr><td style="text-align:left">10月6日</td><td style="text-align:left">朱家尖全日游</td><td style="text-align:left">螺塘线公路、小众海滩探索</td><td style="text-align:left">白天遊玩，晚上开始回程</td><td style="text-align:left">环岛约50km</td></tr><tr><td style="text-align:left">10月6日/7日</td><td style="text-align:left">朱家尖→南京</td><td style="text-align:left">白天继续游玩，傍晚晚饭后开始返程</td><td style="text-align:left">路上视高速堵车情况决定是否停留</td><td style="text-align:left">约500km</td></tr></tbody></table><h2 id="%F0%9F%97%BA%EF%B8%8F-%E8%B7%AF%E7%BA%BF%E5%9C%B0%E5%9B%BE%E4%B8%8E%E5%AF%BC%E8%88%AA%E8%A6%81%E7%82%B9" tabindex="-1">🗺️ 路线地图与导航要点</h2><p>以下是你的行程关键节点示意图：</p><pre>TypeError: Cannot read properties of undefined (reading 'x')</pre><p><strong>关键导航提示</strong>：</p><ul><li><strong>南京到宁波段</strong>：建议使用<strong>G42沪蓉高速→G4012溧宁高速→S26诸永高速→G1522常台高速→G1504宁波绕城</strong>这条线路，比常规的G25长深高速和G15沈海高速车流量小。</li><li><strong>宁波到秀山段</strong>：从镇海区出发，使用<strong>G9211甬舟高速，经舟岱大桥直达岱山，舟岱大桥已于2021年底通车，可实现1小时直达岱山。再经过官山大桥和秀山大桥抵达秀山岛</strong>。</li><li><strong>秀山到朱家尖段</strong>：从秀山岛出发，使用<strong>G9211甬舟高速，经舟山跨海大桥前往朱家尖</strong>。</li><li><strong>返程南京段</strong>：建议使用<strong>G9211甬舟高速→G1504宁波绕城→G92杭州湾环线→S12申嘉湖高速→G25长深高速→G42沪蓉高速</strong>。这条线路比常规的G15沈海高速车流量小，尤其S12申嘉湖高速通常较为畅通。</li></ul><h2 id="%F0%9F%8F%9D%EF%B8%8F-%E6%AF%8F%E6%97%A5%E8%AF%A6%E7%BB%86%E8%A1%8C%E7%A8%8B%E4%B8%8E%E6%B4%BB%E5%8A%A8%E5%AE%89%E6%8E%92" tabindex="-1">🏝️ 每日详细行程与活动安排</h2><h3 id="10%E6%9C%881%E6%97%A5%2F2%E6%97%A5%EF%BC%9A%E5%8D%97%E4%BA%AC-%E2%86%92-%E5%AE%81%E6%B3%A2%E9%95%87%E6%B5%B7%E5%8C%BA" tabindex="-1">10月1日/2日：南京 → 宁波镇海区</h3><ul><li><strong>行车路线</strong>：南京市区 → G42沪蓉高速 → G4012溧宁高速 → S26诸永高速 → G1522常台高速 → G1504宁波绕城 → 镇海区</li><li><strong>出发时间</strong>：<strong>中午12:00后出发</strong>，避开清晨出城高峰，实测中午后车流减少明显。</li><li><strong>预计车程</strong>：约5.5小时（440公里）</li><li><strong>傍晚活动</strong>：到达镇海区后，可前往<strong>宁波最大的海鲜集散地——路林市场</strong>购买价格美丽的海鲜，然后直接用自带的蒸锅吃海鲜，或者找个小餐馆付点加工费帮忙加工处理海鲜。</li><li><strong>住宿推荐</strong>：选择镇海区靠近G9211高速入口的酒店，方便第二天一早前往岱山：<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></ul></li></ul><h3 id="10%E6%9C%883%E6%97%A5%EF%BC%9A%E5%AE%81%E6%B3%A2-%E2%86%92-%E7%A7%80%E5%B1%B1%E5%B2%9B" tabindex="-1">10月3日：宁波 → 秀山岛</h3><ul><li><strong>出发时间</strong>：<strong>早上8:00</strong>（早餐后）</li><li><strong>行车路线</strong>：镇海区 → G9211甬舟高速 → 舟岱大桥 → 岱山岛 → 秀山岛</li><li><strong>预计车程</strong>：约2.5小时（120公里，可能会小堵）</li><li><strong>上午活动</strong>：早餐后（约8点）从宁波出发，导航至秀山。经舟山跨海大桥，沿途已是风光无限。预计车程2.5小时（含可能的小拥堵）。抵达预定的住宿地点<strong>秀水湾休闲度假村私人海景公寓</strong>办理入住（或先存行李）。午餐可在公寓附近简单解决，或去<strong>岱东农贸市场</strong>购买新鲜食材自己动手。</li><li><strong>下午活动</strong>：<strong>燕黄线生态公路自驾</strong>（免费），这是一条沿海红色专业骑行道，一边是层峦叠翠，一边是碧波荡漾的海岛风情。途中可以考虑前往<strong>中国海岬公园</strong>沿岸滩涂（免费，适合赶海），这是一个由多个海岬、海湾串联而成的景观带，非常适合自驾。人少，景美，可以随时停车拍照。<strong>其他的沙滩海景替代方案</strong>：<ul><li><strong>燕黄线</strong>沿途小型沙滩（免费，人少）</li><li><strong>秀山岛滑泥主题公园</strong>附近海滩（免费）</li><li><strong>鹿栏晴沙</strong>（收费，不推荐）</li></ul></li><li><strong>傍晚活动</strong>：<strong>磨心山景区</strong>（门票约39.35元），傍晚时分登上磨心山，俯瞰岱山全景和日落美景。山上还有慈云寺、蓬莱玉佛宝塔等景点。</li><li><strong>晚餐</strong>：前往岱山县高亭镇寻找海鲜排档，尝试当地新鲜海产。</li></ul><h3 id="10%E6%9C%884%E6%97%A5%EF%BC%9A%E5%B2%B1%E5%B1%B1%E5%B2%9B%E5%85%A8%E6%97%A5%E6%8E%A2%E7%B4%A2" tabindex="-1">10月4日：岱山岛全日探索</h3><ul><li><strong>清晨活动</strong>：民宿早起直接观看日出（记得拍延时）。</li><li><strong>上午活动</strong>：<strong>东沙古镇</strong>（门票约30元）：古色古香的街道和建筑，保存着丰富的历史文化遗产，能让人感受到浓郁的历史氛围。古镇的小巷子很出片，可拍组电影感照片。不可错过的：费记海棠糕、海石花、东沙香乾。</li><li><strong>下午活动</strong>：<strong>中国灯塔博物馆</strong>（免费）：了解灯塔历史和文化，馆外草坪拍照很出片。随后可以前往<strong>中国海岬公园</strong>（免费）散步，欣赏海岬风光。</li><li><strong>备选活动</strong>：<ul><li><strong>秀山岛蓝白小镇</strong>（免费）：圣托里尼风建筑+月牙沙滩，拍照效果好（如觉得一般可跳过）。</li><li><strong>衢山岛</strong>一日游（需乘轮渡）：探索灯塔和渔村，还能参与潜水或浮潜活动。</li><li><strong>双合石壁</strong>（门票待确认）：奇特的岩石景观。</li></ul></li><li><strong>晚餐</strong>：尝试岱东镇本地海鲜餐馆，品尝当天捕捞的海鲜。</li></ul><h3 id="10%E6%9C%885%E6%97%A5%EF%BC%9A%E7%A7%80%E5%B1%B1%E5%B2%9B-%E2%86%92-%E6%9C%B1%E5%AE%B6%E5%B0%96" tabindex="-1">10月5日：秀山岛 → 朱家尖</h3><ul><li><strong>出发时间</strong>：<strong>早上9:00</strong>（早餐后）</li><li><strong>行车路线</strong>：秀山岛 → 舟岱大桥 → G9211甬舟高速 → 朱家尖</li><li><strong>预计车程</strong>：约2小时（100公里）</li><li><strong>下午活动</strong>：到达朱家尖后，办理入住，然后开始<strong>大青山滨海环线公路自驾</strong>（大青山国家公园门票约100元/人），这是朱家尖最美的沿海公路，多处观景台可停车拍照。</li><li><strong>傍晚活动</strong>：前往<strong>筲箕湾渔村</strong>（免费）观看日落，这是一个保存较好的传统渔村，适合摄影和体验渔村文化。</li></ul><h3 id="10%E6%9C%886%E6%97%A5%EF%BC%9A%E6%9C%B1%E5%AE%B6%E5%B0%96%E5%85%A8%E6%97%A5%E6%8E%A2%E7%B4%A2" tabindex="-1">10月6日：朱家尖全日探索</h3><ul><li><strong>上午活动</strong>：<strong>螺塘线沿海公路自驾</strong>（免费），这是舟山本岛最美公路之一，比大青山更小众，沿途有多处草坪可停车露营野餐。</li><li><strong>下午活动</strong>：探索朱家尖小众海滩：<ul><li><strong>漳州湾海滩</strong>（免费）：当地人常去的海滩，人少干净</li><li><strong>东沙海滩</strong>（日间票约35元，16:30后半价）：比南沙人少，适合游泳沙滩运动</li><li><strong>乌石塘</strong>（门票约40元）：特色黑色鹅卵石海滩，适合拍照</li></ul></li><li><strong>傍晚活动</strong>：前往<strong>朱家尖观音文化苑</strong>（免费）散步，欣赏海景和佛教文化景观。</li></ul><h3 id="10%E6%9C%886%E6%97%A5%2F7%E6%97%A5%EF%BC%9A%E6%9C%B1%E5%AE%B6%E5%B0%96-%E2%86%92-%E5%8D%97%E4%BA%AC" tabindex="-1">10月6日/7日：朱家尖 → 南京</h3><ul><li><strong>出发时间</strong>：<strong>6号晚上20:00之后 或 7号下午13:00之后</strong>（视高速拥堵情况决定直接回去还是停留一晚）</li><li><strong>行车路线</strong>：朱家尖 → G9211甬舟高速 → G1504宁波绕城 → G92杭州湾环线 → S12申嘉湖高速 → G25长深高速 → G42沪蓉高速 → 南京</li><li><strong>预计车程</strong>：约6-7小时（500公里，不含停留时间）</li></ul><h2 id="%F0%9F%8D%BD%EF%B8%8F-%E7%BE%8E%E9%A3%9F%E6%B8%85%E5%8D%95%E4%B8%8E%E6%8E%A8%E8%8D%90%E9%A4%90%E9%A6%86" tabindex="-1">🍽️ 美食清单与推荐餐馆</h2><h3 id="%E5%B2%B1%E5%B1%B1%E5%B2%9B%E7%BE%8E%E9%A3%9F%E6%8E%A8%E8%8D%90" tabindex="-1">岱山岛美食推荐</h3><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></ol><h3 id="%E6%9C%B1%E5%AE%B6%E5%B0%96%E7%BE%8E%E9%A3%9F%E6%8E%A8%E8%8D%90" tabindex="-1">朱家尖美食推荐</h3><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></ol><h3 id="%E9%95%87%E6%B5%B7%E5%8C%BA%E7%BE%8E%E9%A3%9F%E6%8E%A8%E8%8D%90" tabindex="-1">镇海区美食推荐</h3><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></ol><h2 id="%F0%9F%8F%A8-%E4%BD%8F%E5%AE%BF%E6%8E%A8%E8%8D%90%E4%B8%8E%E5%A4%87%E9%80%89%E6%96%B9%E6%A1%88" tabindex="-1">🏨 住宿推荐与备选方案</h2><h3 id="%E5%AE%81%E6%B3%A2%E9%95%87%E6%B5%B7%E5%8C%BA%E4%BD%8F%E5%AE%BF%E6%8E%A8%E8%8D%90%EF%BC%8810%E6%9C%881%E6%97%A5%2F2%E6%97%A5%EF%BC%89" tabindex="-1">宁波镇海区住宿推荐（10月1日/2日）</h3><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></ol><h3 id="%E5%B2%B1%E5%B1%B1%E5%B2%9B%E4%BD%8F%E5%AE%BF%E5%A4%87%E9%80%89%EF%BC%88%E5%B7%B2%E9%A2%84%E5%AE%9A%E7%A7%80%E6%B0%B4%E6%B9%BE%E7%A7%81%E4%BA%BA%E6%B5%B7%E6%99%AF%E5%85%AC%E5%AF%93%EF%BC%8C%E4%BB%A5%E4%B8%8B%E4%B8%BA%E5%A4%87%E9%80%89%EF%BC%89" tabindex="-1">岱山岛住宿备选（已预定秀水湾私人海景公寓，以下为备选）</h3><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></ol><h3 id="%E6%9C%B1%E5%AE%B6%E5%B0%96%E4%BD%8F%E5%AE%BF%E5%A4%87%E9%80%89%EF%BC%88%E5%B7%B2%E9%A2%84%E5%AE%9A%E6%9C%B1%E5%AE%B6%E5%B0%96%E4%B9%9D%E5%8D%83%E4%B8%83%C2%B7%E7%BE%A4%E5%B2%9B%E6%99%9A%E9%9C%9E%E6%B5%B7%E6%99%AF%E7%BE%8E%E5%AE%BF%EF%BC%8C%E4%BB%A5%E4%B8%8B%E4%B8%BA%E5%A4%87%E9%80%89%EF%BC%89" tabindex="-1">朱家尖住宿备选（已预定朱家尖九千七·群岛晚霞海景美宿，以下为备选）</h3><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></ol><h2 id="%F0%9F%92%B0-%E8%B4%B9%E7%94%A8%E9%A2%84%E4%BC%B0" tabindex="-1">💰 费用预估</h2><h3 id="%E7%94%B5%E8%BD%A6%E8%B4%B9%E7%94%A8%EF%BC%88%E5%B0%8F%E7%B1%B3su7-max%EF%BC%89" tabindex="-1">电车费用（小米SU7 Max）</h3><ul><li><strong>总行程</strong>：约1300公里</li><li><strong>能耗表现</strong>：小米SU7 Max官方电耗约15kWh/100km，实际按20kWh/100km来保守估算</li><li><strong>总耗电量</strong>：1300km × 20kWh/100km = 260kWh</li><li><strong>充电费用</strong>：按平均1元/kWh计算，260kWh × 1元 = <strong>260元</strong></li><li><strong>停车费用</strong>：每个住宿都有免费停车，应该只有景点需要给停车费，按40元来预估计算吧</li><li><strong>充电提示</strong>：镇海区充电设施完善，建议在镇海酒店或附近充电站补充电量，为第二天前往岱山做好准备。</li><li><strong>电车总费用</strong>：约 <strong>300元</strong></li></ul><h3 id="%E4%BD%8F%E5%AE%BF%E8%B4%B9%E7%94%A8" tabindex="-1">住宿费用</h3><ul><li><strong>已确认住宿</strong>：3号-5号四晚住宿（秀山2晚+朱家尖1晚）共计 <strong>1596元</strong></li><li><strong>第一晚住宿</strong>：宁波镇海区酒店按300元/晚计算（从宁波上岛的时候可能考虑洗浴中心过渡一下）</li><li><strong>住宿总费用</strong>：约 <strong>1900元</strong></li></ul><h3 id="%E9%A4%90%E9%A5%AE%E8%B4%B9%E7%94%A8" tabindex="-1">餐饮费用</h3><ul><li><strong>每日餐标</strong>：按每人每天90元计算（2人，早10+午40+晚40）</li><li><strong>餐饮总费用</strong>：90元/人/天 × 2人 × 5天 = <strong>900元</strong></li></ul><h3 id="%E9%97%A8%E7%A5%A8%E8%B4%B9%E7%94%A8" tabindex="-1">门票费用</h3><ul><li><strong>磨心山景区</strong>：39.35元/人 × 2人 ≈ 79元</li><li><strong>东沙古镇</strong>：30元/人 × 2人 = 60元</li><li><strong>大青山国家公园</strong>：100元/人 × 2人 = 200元</li><li><strong>门票总计</strong>：约 <strong>340元</strong></li></ul><h3 id="%E9%A2%9D%E5%A4%96%E5%A4%87%E7%94%A8" tabindex="-1">额外备用</h3><ul><li><strong>其他可能会出现的意外费用</strong>：暂时备用 <strong>500元</strong></li></ul><h3 id="%E6%80%BB%E8%AE%A1%E8%B4%B9%E7%94%A8%E9%A2%84%E4%BC%B0" tabindex="-1">总计费用预估</h3><ul><li><strong>电车费用</strong>：300元</li><li><strong>住宿费用</strong>：1900元</li><li><strong>餐饮费用</strong>：900元</li><li><strong>门票费用</strong>：340元</li><li><strong>额外备用费用</strong>：500元</li><li><strong>总计</strong>：约 <strong>3940元</strong>（2人5天总费用，人均1970元）</li></ul><h2 id="%E2%9A%A0%EF%B8%8F-%E5%A4%87%E9%80%89%E6%96%B9%E6%A1%88%E4%B8%8E%E5%BA%94%E6%80%A5%E8%AE%A1%E5%88%92" tabindex="-1">⚠️ 备选方案与应急计划</h2><h3 id="%E5%A4%A9%E6%B0%94%E5%A4%87%E9%80%89%E6%96%B9%E6%A1%88" tabindex="-1">天气备选方案</h3><ul><li><strong>下雨天备选</strong>：<ul><li>岱山：灯塔博物馆、中国海防博物馆、东沙古镇室内景点、民宿体验</li><li>朱家尖：观音文化苑室内展览、朱家尖民俗博物馆、咖啡馆休闲</li></ul></li><li><strong>台风预警</strong>：密切关注天气预报，如遇台风影响，可提前结束海岛行程，转为宁波或杭州室内游览。</li></ul><h3 id="%E6%8B%A5%E5%A0%B5%E5%A4%87%E9%80%89%E8%B7%AF%E7%BA%BF" tabindex="-1">拥堵备选路线</h3><ul><li><strong>杭州湾跨海大桥拥堵</strong>：可绕行嘉绍大桥（常台高速），虽然距离稍远，但通常车流较少。</li><li><strong>舟山跨海大桥拥堵</strong>：可考虑在宁波多停留一天，游览宁波周边景点（如镇海招宝山、宁波植物园），错峰进岛。</li></ul><h3 id="%E4%BD%8F%E5%AE%BF%E5%A4%87%E9%80%89" tabindex="-1">住宿备选</h3><ul><li>建议下载2-3个酒店预订APP，随时关注住宿可用情况。</li><li>准备1-2个露营备选地点（如合法的露营区域），应对住宿紧张情况。</li></ul><h2 id="%F0%9F%92%8E-%E5%AE%9E%E7%94%A8%E8%B4%B4%E5%A3%AB" tabindex="-1">💎 实用贴士</h2><ol><li><strong>船票预订</strong>：如计划前往衢山岛等需要乘船的岛屿，提前在&quot;浙江省岱山交投客运服务有限公司&quot;公众号购买船票。</li><li><strong>海鲜市场</strong>：购买海鲜可前往沈家门东河市场（下午4点后打折）或岱东农贸市场，价格更为实惠。</li><li><strong>充电规划</strong>：提前下载2-3个充电APP（如星星充电、特来电），规划好沿途充电站。镇海区充电设施较为完善，有多处充电站可选择。</li><li><strong>防晒防蚊</strong>：海岛紫外线强，务必准备防晒用品；傍晚海边蚊虫较多，准备防蚊液。</li><li><strong>现金准备</strong>：部分小餐馆、停车场可能只接受现金，准备一定量现金备用。</li><li><strong>实时交通</strong>：使用高德或百度地图实时路况功能，随时调整路线避开拥堵。</li></ol><p>希望这份优化后的规划能帮助你度过一个愉快且不拥挤的国庆假期！祝你旅途平安，收获满满的海岛美好回忆！</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[如何优化linux服务器过高的内存占用？]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/linux-memory-optimize" />
                <id>tag:https://fx67ll.xyz,2025-09-11:linux-memory-optimize</id>
                <published>2025-09-11T10:28:53+08:00</published>
                <updated>2025-09-11T10:34:49+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>个人应用服务最近都集中部署单台服务器以减少日常开销，所以会长时间保持90%以上的内存占用，向AI学习一下如何优化linux服务器过高的内存占用问题。</p><h1 id="%E9%97%AE%E9%A2%98%E4%B8%80%EF%BC%9A%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96linux%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%87%E9%AB%98%E7%9A%84%E5%86%85%E5%AD%98%E5%8D%A0%E7%94%A8" tabindex="-1">问题一：如何优化linux服务器过高的内存占用</h1><p>你好，完全理解你的处境。在预算有限的情况下，对现有服务器进行优化是运维的必备技能。别担心，从你提供的数据来看，情况并没有看起来那么糟糕。</p><h3 id="%E6%A0%B8%E5%BF%83%E7%BB%93%E8%AE%BA%EF%BC%9A%E4%BD%A0%E7%9A%84%E5%86%85%E5%AD%98%E5%85%B6%E5%AE%9E%E5%A4%9F%E7%94%A8%EF%BC%81" tabindex="-1">核心结论：你的内存其实够用！</h3><p>首先，我们来正确解读你的内存使用情况，这是最关键的一步，能让你安心：</p><p>在Linux系统中，内存管理的原则是：<strong>不用白不用</strong>。它会利用尽可能多的内存来作为磁盘缓存（cache）和缓冲区（buffer），以加速系统的读写速度。当应用程序需要更多内存时，系统会自动释放这部分缓存。</p><ul><li><strong>总内存</strong>: 3719 MB (~3.7GB)</li><li><strong>已用</strong>: 3374 MB — 这个数字<strong>包含了</strong>被用作缓存和缓冲区的内存，所以它看起来很高，但并不可怕。</li><li><strong>可用（available）: 112 MB</strong> — 这是关键指标！这个值指的是<strong>当前真正可供应用程序使用的内存</strong>。它确实偏低了，说明系统正在充分利用内存，但还没到危险的地步。</li><li><strong>buff/cache</strong>: 230 MB — 这就是被用作缓存的内存，可以被快速释放。</li></ul><p>所以，你的服务器目前处于<strong>内存使用紧张但尚未耗尽</strong>的状态。优化目标是释放一些缓存，并找出哪些进程占用了过多内存，从而有针对性地进行配置调整。</p><hr /><h3 id="%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88%EF%BC%88%E6%97%A0%E9%9C%80%E8%8A%B1%E9%92%B1%EF%BC%89" tabindex="-1">优化方案（无需花钱）</h3><h4 id="1.-%E7%AB%8B%E5%8D%B3%E9%87%8A%E6%94%BE%E7%BC%93%E5%AD%98%EF%BC%88%E4%B8%B4%E6%97%B6%E7%BC%93%E8%A7%A3%EF%BC%89" tabindex="-1">1. 立即释放缓存（临时缓解）</h4><p>这是一个安全操作，内核会在需要时自动处理，但手动操作可以立即获得可用内存。</p><pre><code class="language-bash"># 释放 pagecache, dentries and inodessync; echo 3 &gt; /proc/sys/vm/drop_caches</code></pre><p><strong>说明</strong>：</p><ul><li><code>sync</code>：将系统缓冲区数据同步到磁盘，确保数据安全。</li><li><code>echo 1 &gt; ...</code>：只释放页缓存。</li><li><code>echo 2 &gt; ...</code>：只释放目录项和inode缓存。</li><li><code>echo 3 &gt; ...</code>：释放上述所有缓存。<br /><strong>执行后</strong>，你的 <code>可用（available）</code> 内存会立刻增加（<code>buff/cache</code> 会减少）。这是一个临时措施，缓存之后还会慢慢增加。可以在感觉应用变慢时执行。</li></ul><h4 id="2.-%E8%AF%8A%E6%96%AD%E5%88%86%E6%9E%90%EF%BC%9A%E6%89%BE%E5%87%BA%E5%86%85%E5%AD%98%E6%B6%88%E8%80%97%E5%A4%A7%E6%88%B7" tabindex="-1">2. 诊断分析：找出内存消耗大户</h4><p>优化必须有的放矢。请使用以下命令找出真正的“元凶”。</p><p><strong>a. 综合监控</strong></p><pre><code class="language-bash">htop</code></pre><p>或者用经典的 <code>top</code>，然后按 <code>Shift+M</code> 按内存使用排序。查看排在前三的进程是哪些。</p><p><strong>b. 精确查看每个进程的内存</strong></p><pre><code class="language-bash">ps aux --sort=-%mem | head -10</code></pre><p>这个命令会列出内存使用率最高的前10个进程。</p><p><strong>c. 更详细的内存分析工具</strong></p><pre><code class="language-bash">sudo apt install smem # 如果系统是Debian/Ubuntusudo yum install smem # 如果系统是CentOS/RHELsmem -s rss -r | head -10</code></pre><p><code>smem</code> 工具能更准确地报告物理内存占用（RSS、PSS）。</p><h4 id="3.-%E9%92%88%E5%AF%B9%E6%80%A7%E4%BC%98%E5%8C%96%EF%BC%88%E6%A0%B9%E6%8D%AE%E8%AF%8A%E6%96%AD%E7%BB%93%E6%9E%9C%EF%BC%89" tabindex="-1">3. 针对性优化（根据诊断结果）</h4><p>根据第2步的发现，进行以下针对性优化：</p><ul><li><p><strong>如果是 MySQL/MariaDB</strong>：</p><ul><li>检查 <code>/etc/my.cnf</code> 或 <code>/etc/mysql/my.cnf</code> 中的配置。</li><li>关键参数：<code>innodb_buffer_pool_size</code>（通常是最大内存占用项）。如果你的数据库不大，可以将其调小。例如从1G调整为512M甚至256M。</li><li>其他参数：<code>key_buffer_size</code>, <code>query_cache_size</code> (MariaDB 10.3.9+中已弃用)，都可以适当调小。</li><li><strong>建议</strong>：可以先尝试将 <code>innodb_buffer_pool_size</code> 设置为总内存的 1/4 到 1/3（例如 800M - 1.2G）。</li></ul></li><li><p><strong>如果是 Apache/Nginx</strong>：</p><ul><li><strong>Apache</strong>：主要优化进程数和每个进程的内存占用。<ul><li>修改 <code>/etc/apache2/mods-available/mpm_prefork.conf</code>（如果使用prefork模式）。</li><li>降低 <code>MaxRequestWorkers</code>（或旧版本的<code>MaxClients</code>）、<code>StartServers</code>、<code>MinSpareServers</code>、<code>MaxSpareServers</code> 的值。</li></ul></li><li><strong>Nginx</strong>：内存占用通常很低，主要优化PHP进程。</li></ul></li><li><p><strong>如果是 PHP-FPM</strong>（非常常见）：</p><ul><li>修改 <code>/etc/php/7.x/fpm/pool.d/www.conf</code>（版本号可能不同）。</li><li>关键参数：<ul><li><code>pm.max_children</code>：最大子进程数。每个进程会占用一定内存，总数乘以每个进程的内存就是总消耗。<strong>这是最有效的调优参数</strong>。</li><li><code>pm.start_servers</code>：启动时的进程数。</li><li><code>pm.min_spare_servers</code> / <code>pm.max_spare_servers</code>：空闲进程数范围。</li></ul></li><li><strong>如何计算</strong>：假设你的PHP进程平均占用30MB内存，你有1GB内存可供PHP使用（需为系统和其他进程留出空间），那么 <code>max_children</code> 可以设置为 1000MB / 30MB ≈ 33。建议保守设置为25-28，留出余量。</li></ul></li><li><p><strong>如果是其他特定应用</strong>（如Node.js, Java, Python应用）：</p><ul><li>检查其配置文件，通常都有设置内存限制的参数（例如JVM的 <code>-Xmx</code>），尝试将其降低。</li></ul></li></ul><h4 id="4.-%E5%90%AF%E7%94%A8-swap-%E7%A9%BA%E9%97%B4%EF%BC%88%E5%BA%94%E6%80%A5%E6%95%91%E7%94%9F%E8%89%87%EF%BC%89" tabindex="-1">4. 启用 Swap 空间（应急救生艇）</h4><p>Swap是硬盘上的一块空间，当物理内存不足时，系统可以将不常用的内存页换到Swap中。它会影响性能（因为硬盘比内存慢得多），但可以防止程序因内存不足而崩溃。</p><p><strong>检查当前是否有Swap</strong>：</p><pre><code class="language-bash">swapon --showfree -h</code></pre><p><strong>如果没有或者很小，可以添加一个Swap文件</strong>：</p><pre><code class="language-bash"># 1. 创建一个2GB的文件（大小自定，通常是物理内存的0.5到1倍）sudo fallocate -l 2G /swapfile# 2. 设置正确的权限sudo chmod 600 /swapfile# 3. 格式化为swapsudo mkswap /swapfile# 4. 启用它sudo swapon /swapfile# 5. 让它开机自动挂载echo &#39;/swapfile none swap sw 0 0&#39; | sudo tee -a /etc/fstab# 6. 调整 swappiness（可选），这个值（0-100）表示系统使用swap的积极性。越低越倾向于使用物理内存。# 临时设置sudo sysctl vm.swappiness=10# 永久设置echo &#39;vm.swappiness=10&#39; | sudo tee -a /etc/sysctl.conf</code></pre><hr /><h3 id="%E6%80%BB%E7%BB%93%E4%B8%8E%E8%A1%8C%E5%8A%A8%E6%B8%85%E5%8D%95" tabindex="-1">总结与行动清单</h3><ol><li><strong>正确心态</strong>：明白 <code>已用</code> 内存高是Linux正常行为，关注 <code>可用（available）</code>。</li><li><strong>立即行动</strong>：运行 <code>sudo sync; echo 3 &gt; /proc/sys/vm/drop_caches</code> 快速释放缓存，观察效果。</li><li><strong>诊断病因</strong>：运行 <code>htop</code> 或 <code>ps aux --sort=-%mem | head -10</code>，找出前3名内存消耗进程。</li><li><strong>对症下药</strong>：<ul><li>如果是<strong>MySQL</strong>：调低 <code>innodb_buffer_pool_size</code>。</li><li>如果是<strong>PHP-FPM</strong>：调低 <code>pm.max_children</code>。</li><li>如果是<strong>Apache</strong>：调低 <code>MaxRequestWorkers</code> 等参数。</li></ul></li><li><strong>添加保险</strong>：使用上述命令<strong>添加一个2GB的Swap文件</strong>，这是防止应用崩溃的最简单有效的手段。</li><li><strong>长期监控</strong>：养成习惯，定期使用 <code>htop</code> 查看系统状态。</li></ol><p>按照这个流程走一遍，你的服务器内存压力应该能得到显著缓解。这些优化手段在生产环境中非常常见，请放心操作。</p><h1 id="%E9%97%AE%E9%A2%98%E4%BA%8C%EF%BC%9Aswapon-%E6%88%91%E5%B7%B2%E7%BB%8F%E5%88%9B%E5%BB%BA%EF%BC%8C%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E6%9A%82%E6%97%B6%E5%85%88%E4%B8%8D%E7%94%A8%E5%86%8D%E5%A4%84%E7%90%86%E5%86%85%E5%AD%98%E5%8D%A0%E7%94%A8%E7%9A%84%E9%97%AE%E9%A2%98" tabindex="-1">问题二：swapon 我已经创建，是否可以暂时先不用再处理内存占用的问题</h1><p>这是一个非常好的问题，也是很多Linux管理员会有的想法。创建Swap空间确实是一个重要的安全网，但让我们来详细分析一下当前的情况，以帮助你做出最佳决策。</p><h3 id="%E6%A0%B8%E5%BF%83%E7%BB%93%E8%AE%BA%EF%BC%9A%E5%8F%AF%E4%BB%A5%EF%BC%8C%E4%BD%86%E5%BF%85%E9%A1%BB%E6%9C%89%E5%90%8E%E7%BB%AD%E5%8A%A8%E4%BD%9C%E3%80%82" tabindex="-1">核心结论：可以，但必须有后续动作。</h3><p>创建了Swap，相当于给你的服务器买了一份“保险”。它<strong>极大地降低了因为内存耗尽而导致服务崩溃、进程被系统强制杀死的风险</strong>。从这个角度来说，你的服务器<strong>暂时</strong>安全了，你可以不用<strong>立刻、手忙脚乱</strong>地去处理内存问题。</p><p><strong>但是，这绝对不意味着可以高枕无忧，不用再处理内存问题了。</strong> 原因如下：</p><ol><li><strong>性能惩罚严重</strong>：Swap的本质是拿硬盘空间来模拟内存。硬盘（即使是SSD）的读写速度远远低于真正的内存（DDR4内存比SSD快几十甚至上百倍）。当系统开始使用Swap时（称为“换页”），应用程序的响应速度会<strong>显著变慢</strong>。如果你的用户访问网站时感觉卡顿，体验会非常差。</li><li><strong>磁盘I/O压力</strong>：频繁的换页操作会大量读写硬盘，这会占用宝贵的磁盘I/O资源。如果你的服务器上还有数据库或其他需要频繁读写磁盘的应用，它们也会被拖慢，导致整个系统性能下降。</li><li><strong>掩盖了真正的问题</strong>：Swap的存在让内存不足的问题不再“致命”，但它没有解决“为什么内存会不足”这个根本问题。可能是一个配置错误，或者某个进程存在内存泄漏（内存占用随时间不断增长，永不释放），Swap只是暂时掩盖了这些隐患。</li></ol><hr /><h3 id="%E7%BB%99%E4%BD%A0%E7%9A%84%E8%A1%8C%E5%8A%A8%E5%BB%BA%E8%AE%AE%EF%BC%9A%E4%B8%80%E4%B8%AA%E5%88%86%E9%98%B6%E6%AE%B5%E7%9A%84%E7%AD%96%E7%95%A5" tabindex="-1">给你的行动建议：一个分阶段的策略</h3><p>既然已经创建了Swap，你的压力就小了很多。我建议你采取以下步骤：</p><h4 id="%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5%EF%BC%9A%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%A7%82%E5%AF%9F%EF%BC%88%E7%8E%B0%E5%9C%A8%E5%B0%B1%E5%BC%80%E5%A7%8B%EF%BC%89" tabindex="-1">第一阶段：监控与观察（现在就开始）</h4><p>现在有了Swap护航，你可以更从容地观察系统，收集数据，找出根源。</p><ol><li><p><strong>观察Swap的使用情况</strong>：</p><pre><code class="language-bash">free -h# 或者更直观地watch -n 5 &#39;free -h&#39;</code></pre><p>关注 <code>Swap</code> 行下的 <code>used</code> 列。如果这个数字一直在增长，甚至占用了很大一部分，说明物理内存真的不够用了，系统正在频繁依赖Swap，性能已经受到了影响。</p></li><li><p><strong>使用综合工具监控</strong>：</p><pre><code class="language-bash">htop</code></pre><p>在 <code>htop</code> 界面中，你可以同时看到：</p><ul><li>哪些进程占用了最多的内存和CPU。</li><li><strong>内存条和Swap条</strong>的颜色变化。当Swap被使用时，它的颜色会发生变化，非常直观。</li></ul></li><li><p><strong>使用 <code>vmstat</code> 检查换页情况</strong>：</p><pre><code class="language-bash">vmstat 5</code></pre><p><strong>重点关注 <code>si</code> (swap in) 和 <code>so</code> (swap out) 这两列</strong>。如果它们的值<strong>持续大于0</strong>，就意味着系统正在不断地在内存和硬盘之间交换数据，这是性能瓶颈的明确信号。</p></li></ol><h4 id="%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%EF%BC%9A%E6%A0%B9%E6%8D%AE%E7%9B%91%E6%8E%A7%E7%BB%93%E6%9E%9C%E8%BF%9B%E8%A1%8C%E6%A0%B9%E6%9C%AC%E6%80%A7%E4%BC%98%E5%8C%96%EF%BC%88%E5%9C%A8%E6%9C%AA%E6%9D%A5%E5%87%A0%E5%A4%A9%2F%E4%B8%80%E5%91%A8%E5%86%85%E5%AE%8C%E6%88%90%EF%BC%89" tabindex="-1">第二阶段：根据监控结果进行根本性优化（在未来几天/一周内完成）</h4><p>通过第一阶段的监控，你肯定已经找到了内存消耗大户（很可能是MySQL、PHP-FPM或Nginx/Apache）。接下来就按照我们上次讨论的方法进行针对性优化：</p><ul><li><strong>调低 MySQL 的 <code>innodb_buffer_pool_size</code></strong>。</li><li><strong>调低 PHP-FPM 的 <code>pm.max_children</code></strong>。</li><li><strong>检查是否有内存泄漏</strong>：观察某个进程的内存占用是否随时间无限增长。如果是，可能需要重启该服务或检查程序代码。</li></ul><h4 id="%E6%9C%80%E7%BB%88%E7%9B%AE%E6%A0%87" tabindex="-1">最终目标</h4><p>优化的最终目标是：<strong>在保证应用稳定运行的前提下，让 <code>vmstat</code> 中的 <code>si</code> 和 <code>so</code> 几乎始终为0，并且Swap的 <code>used</code> 值长期保持不变或只有极少量使用</strong>。</p><p>这意味着你的物理内存已经足够应付日常业务，系统不再需要频繁访问慢速的硬盘来模拟内存，服务器运行在最佳性能状态。</p><h3 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h3><p><strong>可以暂时不紧张，但绝不能永远不处理。</strong></p><p>你把Swap比作汽车的安全气囊好了。发生碰撞时（内存耗尽），它能救你的命（防止系统崩溃），但你绝对不会想天天看到它弹出来。每次它弹出来，都意味着你的车发生了事故（性能受损）。</p><p>所以，<strong>创建Swap是正确的急救措施，它给了你宝贵的调查和修复时间。请利用好这段时间，找出病根并解决它，而不是永远依赖这个“轮椅”</strong>。</p><h2 id="%E9%97%AE%E9%A2%98%E4%B8%89%EF%BC%9A%E5%A6%82%E4%BD%95%E5%AE%89%E5%85%A8%E5%88%A0%E9%99%A4-swapon" tabindex="-1">问题三：如何安全删除 swapon</h2><p>好的，安全地删除 Swap 文件是一个简单的过程，但必须按照步骤谨慎操作，以确保不会对系统运行中的服务造成影响。</p><p><strong>核心原则：</strong> 首先确保你的系统当前<strong>有足够多的可用物理内存（Available Memory）</strong>，能够承接目前正在使用 Swap 的那部分数据。如果强行删除正在使用的 Swap，可能会导致系统因瞬间内存不足而崩溃。</p><hr /><h3 id="%E5%AE%89%E5%85%A8%E5%88%A0%E9%99%A4-swap-%E7%9A%84%E6%AD%A5%E9%AA%A4" tabindex="-1">安全删除 Swap 的步骤</h3><p>请严格按照以下顺序操作：</p><h4 id="%E7%AC%AC-1-%E6%AD%A5%EF%BC%9A%E6%A3%80%E6%9F%A5%E5%BD%93%E5%89%8D-swap-%E4%BD%BF%E7%94%A8%E6%83%85%E5%86%B5" tabindex="-1">第 1 步：检查当前 Swap 使用情况</h4><p>首先，你需要确认系统当前是否正在使用 Swap。如果 Swap 使用量为 0，那么可以安全地立即禁用。如果正在使用，则需要先将其中的数据转移回物理内存。</p><pre><code class="language-bash">free -h</code></pre><p>查看输出中 <code>Swap</code> 那一行的 <code>used</code> 列。如果 <code>used</code> 的值不是 <code>0</code>（例如 <code>2.1G</code>），说明有数据正在 Swap 中。<br /><strong>同时，使用 <code>vmstat</code> 检查系统换页活动：</strong></p><pre><code class="language-bash">vmstat 2 5</code></pre><ul><li>如果 <code>si</code> (swap in) 和 <code>so</code> (swap out) 两列的值大部分时间都是 <code>0</code>，说明系统当前没有频繁地使用 Swap，此时操作更安全。</li><li>如果 <code>si</code> 有数值，说明系统正在从 Swap 读数据到内存，这是你可以操作的时机。</li><li><strong>如果 <code>so</code> 有数值，说明系统正在被迫将内存数据换出到 Swap，这意味着物理内存不足！此时绝对不能禁用 Swap！</strong></li></ul><h4 id="%E7%AC%AC-2-%E6%AD%A5%EF%BC%88%E5%8F%AF%E9%80%89%E4%BD%86%E6%8E%A8%E8%8D%90%EF%BC%89%EF%BC%9A%E9%87%8A%E6%94%BE%E7%B3%BB%E7%BB%9F%E7%BC%93%E5%AD%98" tabindex="-1">第 2 步（可选但推荐）：释放系统缓存</h4><p>这是一个很好的做法，可以手动释放一些缓存页，为物理内存腾出更多空间，以便系统能更容易地将 Swap 中的数据转移回来。</p><pre><code class="language-bash">sudo sync; echo 3 &gt; /proc/sys/vm/drop_caches</code></pre><p>再次运行 <code>free -h</code>，观察 <code>Swap</code> 的 <code>used</code> 值是否下降，<code>Available</code> 物理内存是否上升。</p><h4 id="%E7%AC%AC-3-%E6%AD%A5%EF%BC%9A%E7%A6%81%E7%94%A8-swap-%E6%96%87%E4%BB%B6" tabindex="-1">第 3 步：禁用 Swap 文件</h4><p>现在，你可以安全地关闭 Swap 文件了。这个操作会立即将 Swap 中的数据写回物理内存（只要物理内存足够）。</p><pre><code class="language-bash">sudo swapoff -v /swapfile</code></pre><ul><li><code>-v</code> 参数表示详细模式，会让你看到操作进度。</li><li><strong>请注意：</strong> 如果你的 Swap 使用量很大（例如好几个GB），这个操作可能会<strong>花费几十秒到几分钟</strong>，期间系统会变得非常繁忙（I/O 等待很高），这是正常现象。耐心等待命令完成，<strong>切勿中途中断</strong>。</li></ul><h4 id="%E7%AC%AC-4-%E6%AD%A5%EF%BC%9A%E9%AA%8C%E8%AF%81-swap-%E5%B7%B2%E7%A6%81%E7%94%A8" tabindex="-1">第 4 步：验证 Swap 已禁用</h4><p>操作完成后，再次检查确认 Swap 已被成功关闭。</p><pre><code class="language-bash">free -hswapon --show</code></pre><ul><li><code>free -h</code> 的输出中，<code>Swap</code> 行的 <code>total</code> 和 <code>used</code> 应该都变为 <code>0</code>。</li><li><code>swapon --show</code> 应该没有任何输出，表示系统已无激活的 Swap 空间。</li></ul><h4 id="%E7%AC%AC-5-%E6%AD%A5%EF%BC%9A%E4%BB%8E-%2Fetc%2Ffstab-%E4%B8%AD%E7%A7%BB%E9%99%A4%E5%90%AF%E5%8A%A8%E9%A1%B9" tabindex="-1">第 5 步：从 <code>/etc/fstab</code> 中移除启动项</h4><p>这是非常关键的一步，防止下次重启后系统又自动挂载这个 Swap 文件。<br />使用文本编辑器（如 <code>nano</code> 或 <code>vim</code>）打开 <code>/etc/fstab</code> 文件：</p><pre><code class="language-bash">sudo nano /etc/fstab</code></pre><p>找到添加 Swap 文件时写入了那一行，它通常长这样：</p><pre><code class="language-">/swapfile none swap sw 0 0</code></pre><p>将这一行<strong>直接删除</strong>，或者在其行首添加一个 <code>#</code> 符号将其注释掉：</p><pre><code class="language-"># /swapfile none swap sw 0 0</code></pre><p>保存并退出编辑器（在 nano 中是 <code>Ctrl+X</code>，然后按 <code>Y</code> 确认，再按回车）。</p><h4 id="%E7%AC%AC-6-%E6%AD%A5%EF%BC%88%E6%9C%80%E7%BB%88%E6%AD%A5%E9%AA%A4%EF%BC%89%EF%BC%9A%E5%88%A0%E9%99%A4-swap-%E6%96%87%E4%BB%B6" tabindex="-1">第 6 步（最终步骤）：删除 Swap 文件</h4><p>既然已经确保系统不再使用它，你就可以安全地删除这个文件来释放磁盘空间了。</p><pre><code class="language-bash">sudo rm /swapfile</code></pre><hr /><h3 id="%E6%80%BB%E7%BB%93%E4%B8%8E%E6%9C%80%E7%BB%88%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95" tabindex="-1">总结与最终检查清单</h3><ol><li><strong>检查</strong>：<code>free -h</code> 和 <code>vmstat</code> 确认系统状态。</li><li><strong>释放缓存</strong>：（可选）<code>sudo sync; echo 3 &gt; /proc/sys/vm/drop_caches</code></li><li><strong>禁用 Swap</strong>：<code>sudo swapoff -v /swapfile</code> （耐心等待完成）</li><li><strong>验证</strong>：再次运行 <code>free -h</code> 和 <code>swapon --show</code> 确认已禁用。</li><li><strong>清理配置</strong>：编辑 <code>/etc/fstab</code>，删除或注释掉 Swap 相关行。</li><li><strong>删除文件</strong>：<code>sudo rm /swapfile</code></li></ol><p>完成以上所有步骤后，你的 Swap 文件就被安全且彻底地删除了。之后请继续关注系统的内存使用情况（<code>htop</code>），确保之前进行的内存优化（调整 MySQL、PHP 等配置）是有效的，物理内存足以支撑你的应用正常运行。</p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/blob/master/serve-blog/2025/2025-09/linux-memory-optimize.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2025 为什么不选AI?]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/why-not-ai-2025" />
                <id>tag:https://fx67ll.xyz,2025-08-15:why-not-ai-2025</id>
                <published>2025-08-15T17:16:20+08:00</published>
                <updated>2025-09-24T17:17:38+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="%E6%88%91%E4%BB%AC%E7%9A%84%E4%BA%A7%E5%93%81%E7%BA%BF%E6%80%BB%E7%BB%8F%E7%90%86%E8%AF%B4%E4%BA%86%E4%BB%A5%E4%B8%8B%E7%9A%84%E8%AF%9D%EF%BC%8C%E8%AF%B7%E9%97%AE%E5%A6%82%E4%BD%95%E8%A7%A3%E8%AF%BB%E4%BB%96%E5%8F%91%E8%BF%99%E6%AE%B5%E8%AF%9D%E7%9A%84%E8%83%8C%E6%99%AF%EF%BC%8C%E6%88%91%E8%AF%A5%E5%A6%82%E4%BD%95%E6%8F%90%E5%8D%87%E8%87%AA%E5%B7%B1%EF%BC%9F" tabindex="-1">我们的产品线总经理说了以下的话，请问如何解读他发这段话的背景，我该如何提升自己？</h1><pre><code class="language-">希望所有同学真正重视起来，把AI作为工具的同时，需要全面提升指挥AI为我们干活的能力。那么，一个非常基础的问题摆在面前：你如何指挥AI帮你干活？它会对你的专业能力提出更大的挑战。展望未来，我们不缺解决问题的AI，还是缺创意、缺想法、缺发现问题的能力。AI工具层出不穷，所能达成的任务和目标越来越具备生产力。我们现有的60%以上的工作会被更高效率、更高质量的AI工具所取代。大家要思考一个问题：接下来，需要我做什么？为什么需要我？为什么是我？</code></pre><h2 id="%E8%A7%A3%E8%AF%BB" tabindex="-1">解读</h2><p>从产品线总经理的这段话中，可以清晰感受到当前企业正处于<strong>AI驱动的生产力革命临界点</strong>。结合行业趋势与技术发展，其核心背景可从三个维度解读：</p><h3 id="%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF%E8%A7%A3%E8%AF%BB%EF%BC%9A%E4%BC%81%E4%B8%9A%E8%BD%AC%E5%9E%8B%E7%9A%84%E6%B7%B1%E5%B1%82%E9%80%BB%E8%BE%91" tabindex="-1">一、背景解读：企业转型的深层逻辑</h3><ol><li><p><strong>技术渗透的不可逆性</strong><br />2025年AI已从“辅助工具”升级为“决策伙伴”。根据腾讯云趋势报告，AI Agent（智能体）正成为企业级生产力工具，具备自主规划、跨系统协作能力。例如，制造业中AI可通过视觉-语言-动作多模态模型实现生产线自主优化，这意味着传统执行层岗位的工作范式将被重构。总经理提到的“60%工作被取代”并非危言耸听——麦肯锡报告显示，基础数据处理、客服等标准化工作已有超60%环节可被AI替代。</p></li><li><p><strong>价值创造的范式转移</strong><br />当AI能高效完成“解决问题”的任务时，<strong>定义问题的能力成为新的竞争壁垒</strong>。以生成式AI为例，其已能根据提示词生成营销文案、数据分析报告甚至代码，但这依赖于人类明确的需求拆解能力。正如某二本机械专业学员通过4个月学习AI调优技能，薪资翻3倍入职医疗AI公司，这印证了“指挥AI”能力的溢价空间。</p></li><li><p><strong>组织能力的重构压力</strong><br />企业正从“人力密集型”向“AI增强型”转型。海尔工厂通过“机械操作+AI故障诊断”复合技能培训，实现人效提升2.3倍，这说明公司可能面临岗位再设计（如设立“AI训练师”“人机协作督导”）和流程重组。总经理的发言本质上是在推动员工从“被执行者”向“AI管理者”角色跃迁。</p></li></ol><h3 id="%E4%BA%8C%E3%80%81%E8%83%BD%E5%8A%9B%E8%B7%83%E8%BF%81%EF%BC%9A%E4%BB%8E%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8%E8%80%85%E5%88%B0ai%E6%8C%87%E6%8C%A5%E5%AE%98" tabindex="-1">二、能力跃迁：从工具使用者到AI指挥官</h3><h4 id="%EF%BC%88%E4%B8%80%EF%BC%89%E5%9F%BA%E7%A1%80%E8%83%BD%E5%8A%9B%EF%BC%9A%E6%9E%84%E5%BB%BAai%E5%8D%8F%E4%BD%9C%E7%9A%84%E2%80%9C%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E2%80%9D" tabindex="-1">（一）基础能力：构建AI协作的“操作系统”</h4><ol><li><p><strong>精准指令的工程化思维</strong></p><ul><li><strong>结构化沟通</strong>：采用“角色设定+背景输入+任务拆解”的三层指令框架。例如，要求AI生成市场报告时，可明确：“你是有10年经验的市场总监，分析我们新推出的智能咖啡壶，需包含三个典型用户画像（含价值观、痛点）、5个核心消费场景、渠道投放优先级”。</li><li><strong>迭代优化</strong>：避免一次性提问，通过“少样本提示”（提供范例）和“思维链引导”（要求分步思考）逐步校准输出。例如，让AI生成代码时，先给出类似接口的实现案例，再要求其遵循相同架构逻辑。</li></ul></li><li><p><strong>数据素养与伦理意识</strong></p><ul><li><strong>数据驱动决策</strong>：掌握基础Python数据分析能力，能解读AI输出的逻辑（如识别模型偏见）。例如，某银行客服通过学习数据可视化工具，将AI生成的客户情绪分析转化为服务优化建议。</li><li><strong>伦理决策能力</strong>：欧盟已强制要求企业配备AI伦理审查师，员工需学习算法公平性评估框架，避免因AI决策引发的合规风险。</li></ul></li></ol><h4 id="%EF%BC%88%E4%BA%8C%EF%BC%89%E9%AB%98%E9%98%B6%E8%83%BD%E5%8A%9B%EF%BC%9A%E7%AA%81%E7%A0%B4ai%E7%9A%84%E8%83%BD%E5%8A%9B%E8%BE%B9%E7%95%8C" tabindex="-1">（二）高阶能力：突破AI的能力边界</h4><ol><li><p><strong>创意的结构化输出</strong></p><ul><li><strong>问题定义能力</strong>：通过“假设-验证”循环挖掘需求本质。例如，当AI生成的营销文案转化率低时，需拆解问题：是目标用户画像不准确（需重新调研），还是情感共鸣点未捕捉（需调整提示词中的价值观描述）。</li><li><strong>跨模态创新</strong>：利用多模态AI实现“从词元到体素”的突破。如设计师可结合文本描述与3D建模AI，快速生成产品原型并进行虚拟测试。</li></ul></li><li><p><strong>人机协作的生态构建</strong></p><ul><li><strong>角色定位转型</strong>：从“AI使用者”升级为“AI训练师”。例如，客服人员可通过标注客户对话数据优化智能客服模型，同时转向处理复杂投诉（AI无法应对的情感交互场景）。</li><li><strong>流程重构能力</strong>：参考德勤“人类-AI小组制”，将重复性任务（如数据录入）交由AI，人类聚焦客户需求洞察与解决方案设计。</li></ul></li><li><p><strong>战略级问题解决</strong></p><ul><li><strong>系统思维培养</strong>：识别AI应用的“杠杆点”。例如，某物流企业员工通过分析AI生成的运输路径优化方案，发现可通过调整仓库布局进一步降低成本，从而推动整体供应链效率提升。</li><li><strong>未来场景预演</strong>：运用AI进行“压力测试”。如产品经理可通过模拟不同市场环境下的用户反馈，提前规划产品迭代路径。</li></ul></li></ol><h3 id="%E4%B8%89%E3%80%81%E8%A1%8C%E5%8A%A8%E8%B7%AF%E5%BE%84%EF%BC%9A%E4%BB%8E%E2%80%9C%E5%B9%B8%E5%AD%98%E8%80%85%E2%80%9D%E5%88%B0%E2%80%9C%E5%AE%9A%E4%B9%89%E8%80%85%E2%80%9D" tabindex="-1">三、行动路径：从“幸存者”到“定义者”</h3><ol><li><p><strong>能力升级的三级阶梯</strong></p><ul><li><strong>初级阶段（3-6个月）</strong>：掌握Prompt工程（如6步提示词框架）、基础数据分析工具（Excel+Python），考取AI工具认证（如Google AI证书）。</li><li><strong>中级阶段（6-12个月）</strong>：参与跨部门AI项目（如流程自动化改造），学习AIGC工具链（如Midjourney+Notion AI），培养“技术+业务”双视角。</li><li><strong>高级阶段（1年以上）</strong>：主导AI驱动的创新项目（如开发行业垂直模型），学习AI伦理与治理框架，成为“AI+行业”复合型人才。</li></ul></li><li><p><strong>组织适配的策略选择</strong></p><ul><li><strong>主动重构工作流</strong>：参考海尔“机械操作+AI故障诊断”模式，将现有任务拆解为“AI执行层”与“人类决策层”。例如，市场人员可让AI生成10版广告文案，自己聚焦筛选与价值观校准。</li><li><strong>构建个人知识体系</strong>：建立“AI协作档案”，记录每次与AI的交互过程（如哪些提示词有效、哪些场景需人工复核），形成可复用的“人机协作SOP”。</li></ul></li><li><p><strong>差异化竞争力打造</strong></p><ul><li><strong>成为π型人才</strong>：在专业领域（如产品设计）基础上，叠加AI工具驾驭能力与商业洞察。例如，设计师可通过学习AI生成3D模型，同时掌握用户体验数据分析，实现从“执行者”到“体验架构师”的跨越。</li><li><strong>抢占伦理高地</strong>：主动学习AI伦理框架（如欧盟AI法案），在项目中引入偏见检测与可解释性评估，成为团队中的“AI合规守护者”。</li></ul></li></ol><h3 id="%E5%9B%9B%E3%80%81%E7%BB%88%E6%9E%81%E5%91%BD%E9%A2%98%EF%BC%9A%E9%87%8D%E6%96%B0%E5%AE%9A%E4%B9%89%E4%BA%BA%E7%B1%BB%E4%BB%B7%E5%80%BC" tabindex="-1">四、终极命题：重新定义人类价值</h3><p>总经理反复强调的“创意、想法、发现问题”，本质上指向<strong>人类独有的元能力</strong>。当AI能处理90%的已知问题时，剩下的10%恰恰是决定企业竞争力的“战略留白”。正如某跨国企业转型案例所示，那些将客户沟通经验与养老规划知识结合的员工，创造了AI无法复制的情感价值。</p><p><strong>未来的职业护城河，在于你能否用AI拓展人类能力的边界</strong>：</p><ul><li><strong>用AI放大创意</strong>：让AI生成100个产品概念，你从中提炼出第101个颠覆性想法；</li><li><strong>用AI验证假设</strong>：通过模拟市场反应，快速迭代商业模式；</li><li><strong>用AI发现盲区</strong>：当AI输出“无解”时，恰恰是突破认知局限的契机。</li></ul><p>在这场变革中，真正的赢家不是“对抗AI”的人，而是<strong>重新定义AI与人类协作方式</strong>的人。正如麦肯锡报告揭示的，掌握AI工具的从业者薪资溢价达34%，而那些能将AI转化为商业价值的“AI指挥官”，将成为下一个十年的职场稀缺物种。</p><h2 id="%E6%8F%90%E5%8D%87" tabindex="-1">提升</h2><p>理解你作为全栈开发工程师的处境——你们是AI冲击的<strong>前沿阵地</strong>，也是人机协作的<strong>关键枢纽</strong>。结合总经理的要求，以下是为你量身定制的核心能力升级路径：</p><hr /><h3 id="%E2%9A%99%EF%B8%8F-%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91%E5%B7%A5%E7%A8%8B%E5%B8%88%E7%9A%84ai%E6%97%B6%E4%BB%A3%E7%94%9F%E5%AD%98%E6%B3%95%E5%88%99" tabindex="-1">⚙️ <strong>全栈开发工程师的AI时代生存法则</strong></h3><h4 id="%E6%A0%B8%E5%BF%83%E5%AE%9A%E4%BD%8D%E8%BD%AC%E5%9E%8B%EF%BC%9A%E4%BB%8E%E2%80%9C%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0%E8%80%85%E2%80%9D-%E2%86%92-%E2%80%9Cai%E9%A9%B1%E5%8A%A8%E7%9A%84%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E5%B8%88%E2%80%9D" tabindex="-1"><strong>核心定位转型：从“代码实现者” → “AI驱动的系统架构师”</strong></h4><blockquote><p><strong>关键认知：</strong><br /><strong>AI能生成代码，但无法理解业务复杂性、权衡技术决策、预判系统熵增——这恰是你的战场。</strong></p></blockquote><hr /><h3 id="%F0%9F%94%A5-%E4%BD%A0%E5%BF%85%E9%A1%BB%E4%BC%98%E5%85%88%E5%BC%BA%E5%8C%96%E7%9A%844%E5%A4%A7%E8%83%BD%E5%8A%9B" tabindex="-1">🔥 你必须优先强化的4大能力</h3><h4 id="1.-ai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%AE%BE%E8%AE%A1%E8%83%BD%E5%8A%9B%EF%BC%88%E6%A0%B8%E5%BF%83%E7%94%9F%E4%BA%A7%E5%8A%9B%EF%BC%89" tabindex="-1">1. <strong>AI工作流设计能力（核心生产力）</strong></h4><ul><li><strong>痛点：</strong> 全栈工作链条长（前端+后端+数据库+部署），碎片化任务最易被AI切割取代</li><li><strong>突围策略：</strong><pre><code class="language-">mermaidgraph LRA[需求分析] --&gt; B[AI生成伪代码/接口草案]B --&gt; C[人工判断技术可行性]C --&gt; D[AI生成模块代码]D --&gt; E[人工进行系统集成测试]E --&gt; F[AI优化性能/修复基础bug]F --&gt; G[人工做技术债务评估]</code></pre></li><li><strong>立即行动：</strong><ul><li>用 <strong>Cursor</strong> 或 <strong>Github Copilot</strong> 重构老旧代码：<code>/重构 此函数 要求内存降低30%</code></li><li>用 <strong>ChatGPT</strong> 设计技术方案：<br /><em>“作为全栈工程师，我需要实现高并发秒杀系统。请列出技术选型对比表，包含Redis集群 vs Kafka的吞吐量成本权衡”</em></li></ul></li></ul><h4 id="2.-%E6%8A%80%E6%9C%AF%E5%86%B3%E7%AD%96%E4%B8%8E%E9%A3%8E%E9%99%A9%E9%A2%84%E5%88%A4%E8%83%BD%E5%8A%9B%EF%BC%88ai%E6%97%A0%E6%B3%95%E6%9B%BF%E4%BB%A3%EF%BC%89" tabindex="-1">2. <strong>技术决策与风险预判能力（AI无法替代）</strong></h4><ul><li><strong>为何关键：</strong> AI会给出10种数据库方案，但<strong>无法判断哪种最适合你公司当前运维能力</strong></li><li><strong>训练方法：</strong><ul><li>每次技术选型时，强制自己写 <strong>三维评估卡</strong>：<table><thead><tr><th>维度</th><th>AI建议方案</th><th>你的修正方案</th><th>修正理由</th></tr></thead><tbody><tr><td><strong>短期成本</strong></td><td>直接用Serverless</td><td>自建K8s集群</td><td>预计3个月后流量增长200%</td></tr><tr><td><strong>团队能力</strong></td><td>推荐GraphQL</td><td>保留RESTful</td><td>团队无GraphQL维护经验</td></tr><tr><td><strong>技术债务</strong></td><td>快速实现</td><td>增加抽象层</td><td>预判未来需支持多支付渠道</td></tr></tbody></table></li></ul></li><li><strong>实战工具：</strong><br />用 <strong>Mermaid语法</strong> 让AI生成系统架构图 → <strong>你标注风险点</strong>（如单点故障、潜在瓶颈）</li></ul><h4 id="3.-%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E7%BF%BB%E8%AF%91%E8%83%BD%E5%8A%9B%EF%BC%88%E6%8A%A4%E5%9F%8E%E6%B2%B3%EF%BC%89" tabindex="-1">3. <strong>业务逻辑翻译能力（护城河）</strong></h4><ul><li><strong>残酷现实：</strong> 产品经理说“用户要更流畅的体验”，AI可能直接给出前端懒加载方案——<strong>但真实需求可能是后端响应慢！</strong></li><li><strong>提升步骤：</strong><ol><li>用AI将模糊需求转化为技术用例：<br /><em>“将‘购物车加载太慢’转化为后端API优化+前端渲染的5种技术方案”</em></li><li><strong>添加业务约束条件：</strong><br /><em>“方案必须兼容旧版安卓机（市场份额15%），且下周上线”</em></li><li>对比AI方案与你直觉方案的差异，迭代业务理解能力</li></ol></li></ul><h4 id="4.-%E8%B7%A8%E7%95%8C%E7%B3%BB%E7%BB%9F%E8%BF%9E%E6%8E%A5%E8%83%BD%E5%8A%9B%EF%BC%88%E5%88%9B%E6%96%B0%E7%88%86%E5%8F%91%E7%82%B9%EF%BC%89" tabindex="-1">4. <strong>跨界系统连接能力（创新爆发点）</strong></h4><ul><li><strong>案例启示：</strong> Airbnb工程师用<strong>计算机视觉+订单系统</strong>自动检测房源照片真实性</li><li><strong>具体路径：</strong><ul><li><strong>每月渗透一个邻近领域：</strong><table><thead><tr><th>月份</th><th>学习领域</th><th>与全栈开发的连接点</th><th>AI辅助方式</th></tr></thead><tbody><tr><td>本月</td><td>DevOps</td><td>用AI写K8s部署脚本</td><td>让ChatGPT解释Helm Chart原理</td></tr><tr><td>下月</td><td>数据工程</td><td>用PySpark优化报表查询</td><td>让AI对比Lambda架构优劣</td></tr><tr><td>下下月</td><td>硬件交互</td><td>开发IoT设备控制面板</td><td>生成Arduino通信协议代码</td></tr></tbody></table></li></ul></li><li><strong>产出要求：</strong> 每学完一个领域，用AI辅助开发<strong>一个小型融合项目</strong>（如：用NextJS+TensorFlow.js实现浏览器端图片识别）</li></ul><hr /><h3 id="%F0%9F%9B%A0%EF%B8%8F-%E5%85%A8%E6%A0%88%E5%B7%A5%E7%A8%8B%E5%B8%88%E7%9A%84ai%E5%B7%A5%E5%85%B7%E7%AE%B1%EF%BC%88%E7%AB%8B%E5%8D%B3%E8%A3%85%E5%A4%87%EF%BC%89" tabindex="-1">🛠️ 全栈工程师的AI工具箱（立即装备）</h3><table><thead><tr><th><strong>场景</strong></th><th><strong>推荐工具</strong></th><th><strong>增效案例</strong></th></tr></thead><tbody><tr><td>代码生成与重构</td><td>Cursor / Github Copilot</td><td>30分钟完成原本2小时的CRUD模块开发</td></tr><tr><td>技术方案设计</td><td>ChatGPT + Mermaid插件</td><td>自动输出系统架构图+SQL优化建议</td></tr><tr><td>故障诊断</td><td>Warp AI / Bash Ninja</td><td>输入错误日志直接获取修复方案</td></tr><tr><td>性能优化</td><td><a href="http://Phind.com" target="_blank">Phind.com</a></td><td>输入瓶颈描述获取数据库索引优化策略</td></tr><tr><td>安全审计</td><td>Semgrep AI</td><td>自动检测代码中的SQL注入风险点</td></tr></tbody></table><hr /><h3 id="%F0%9F%9A%80-90%E5%A4%A9%E8%9C%95%E5%8F%98%E8%AE%A1%E5%88%92%EF%BC%88%E4%BB%8E%E2%80%9C%E8%A2%ABai%E5%A8%81%E8%83%81%E2%80%9D%E5%88%B0%E2%80%9C%E7%94%A8ai%E7%A0%B4%E5%B1%80%E2%80%9D%EF%BC%89" tabindex="-1">🚀 90天蜕变计划（从“被AI威胁”到“用AI破局”）</h3><table><thead><tr><th><strong>时间轴</strong></th><th><strong>行动重点</strong></th><th><strong>交付成果</strong></th><th><strong>验证标准</strong></th></tr></thead><tbody><tr><td><strong>第1-30天</strong></td><td>用AI接管重复编码任务</td><td>释放40%编码时间</td><td>领导认可你承担更复杂任务</td></tr><tr><td><strong>第31-60天</strong></td><td>主导1次技术方案重构</td><td>输出带AI协作标注的设计文档</td><td>方案被采纳且降低后续维护成本30%</td></tr><tr><td><strong>第61-90天</strong></td><td>发起1个跨界创新项目</td><td>上线融合新技术的最小可行产品(MVP)</td><td>获得跨部门资源支持</td></tr></tbody></table><hr /><h3 id="%F0%9F%92%A1-%E7%BB%88%E6%9E%81%E5%9B%9E%E7%AD%94%E2%80%9C%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E6%88%91%E2%80%9D%E7%9A%84%E7%AD%94%E6%A1%88" tabindex="-1">💡 终极回答“为什么需要我”的答案</h3><blockquote><p><strong>当AI能写单点代码时，你是那个看见系统复杂性的人；</strong><br /><strong>当AI堆砌技术方案时，你是那个预判技术债务的人；</strong><br /><strong>当AI满足表面需求时，你是那个洞察业务本质的人。</strong></p></blockquote><p><strong>明天就做这件事：</strong><br />在下一个需求评审会上，<strong>主动用AI工具现场演示</strong>：</p><ol><li>将产品需求实时转化为技术用例</li><li>生成带风险标注的架构草图</li><li>预估不同方案的技术债务值<br />👉 你会瞬间成为“AI时代的全栈新物种”</li></ol><p>需要具体场景（如微服务优化/性能调优）的AI协作方案，我可以继续深度拆解。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2024 新车维保记录]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2024-vehicle-maintain" />
                <id>tag:https://fx67ll.xyz,2025-08-14:2024-vehicle-maintain</id>
                <published>2025-08-14T12:00:00+08:00</published>
                <updated>2025-09-26T16:10:57+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="2024-%E6%96%B0%E8%BD%A6%E7%BB%B4%E4%BF%9D%E8%AE%B0%E5%BD%95" tabindex="-1">2024 新车维保记录</h1><h2 id="%E5%89%8D%E8%A8%80" tabindex="-1">前言</h2><p>对于电动车而言，科学的日常养护是保证其长期性能与安全的关键。</p><p>本文不仅是一份4万公里保养清单，更是我个人在过去一年里，基于官方建议与实践经验，为自己建立一份可追溯的车辆健康档案，让每次保养决策都有据可依。</p><p>希望这份持续更新的记录，能帮助我们在享受科技便利的同时，也能成为使用车的 <strong>“长期主义者”</strong>。</p><div style="margin-bottom: 40px;"></div><h2 id="%E4%B8%80%E3%80%81%E6%A0%B8%E5%BF%83%E4%BF%9D%E5%85%BB%E5%8E%9F%E5%88%99" tabindex="-1">一、核心保养原则</h2><h3 id="1.-%E4%B8%89%E5%85%83%E9%94%82%E7%94%B5%E6%B1%A0%E6%97%A5%E5%B8%B8%E4%BC%98%E5%8C%96%E7%AD%96%E7%95%A5" tabindex="-1">1. 三元锂电池日常优化策略</h3><p><strong>麒麟三元锂电池的长期养护需遵循 “浅充浅放、控温限压、定期校准” 的核心原则。通过科学管理电量、优化充电参数、适应环境温度并结合定期维护，可显著延长电池寿命，实现 10 年 / 30 万公里以上的可靠使用。用户应根据车辆实际使用场景动态调整策略，在寿命与便利性之间找到平衡。</strong></p><ol><li>优先选择地下车库或阴凉处，避免阳光直射。</li><li>尽量少用快充，优先使用 3.3kw 慢充。行驶后电池温度较高，静置 30 分钟以上再充电，避免高温叠加。</li><li>日常使用保持在 20% - 80% SOC 区间，避免深度充放电。若需长途出行，可临时充至 100%，但需在 24 小时内将电量降至 90% 以下，减少高电压暴露时间。</li><li>每月进行一次 浅循环校准：用慢充从 20% 充至 90%，期间避免中途断电。每季度进行一次 深度校准：将电量放至 15% 以下，静置 4 小时后慢充至 100%，触发 BMS 均衡功能。校准过程中需确保车辆处于休眠状态（禁用手机 APP 唤醒），以提高电量估算精度。</li></ol><div style="margin-bottom: 40px;"></div><h3 id="2.-%E7%B1%B3%E5%85%B6%E6%9E%97psev%E8%BD%AE%E8%83%8E%E6%97%A5%E5%B8%B8%E4%BF%9D%E5%85%BB%E7%AD%96%E7%95%A5" tabindex="-1">2. 米其林PSEV轮胎日常保养策略</h3><p><strong>米其林 Pilot Sport EV（前 245/40R20、后 265/40R20） 轮胎规格是前后规格差异设计，且是 EV 专属胎，需重点规避 “维护不当” “局部损伤” 等风险。核心注意事项可浓缩为：“查胎压、清石子、防鼓包、盯 2.5mm”</strong></p><h4 id="a.-%E5%AE%9A%E6%9C%9F%E6%A3%80%E6%9F%A5%E8%83%8E%E5%8E%8B%EF%BC%9Aev%E8%BD%A6%E5%9E%8B%E5%AF%B9%E8%83%8E%E5%8E%8B%E6%9B%B4%E6%95%8F%E6%84%9F" tabindex="-1">a. 定期检查胎压：EV车型对胎压更敏感</h4><p>米其林Pilot Sport EV作为电动车专用胎，需匹配EV车型的&quot;重车身（电池重量大）、高扭矩&quot;特性，胎压不当会直接影响磨损寿命和安全性：</p><ul><li>胎压标准：官方推荐冷胎压 <strong>2.9bar</strong>；</li><li>危害：① 胎压不足：会加剧胎肩磨损（两侧花纹磨得快），同时增加滚阻（导致电动车续航下降）；② 胎压过高：会让胎面中间磨损快，且降低湿地抓地力（雨天易打滑）；</li><li>频率：建议每月检查1次，长途前必查（温差大时胎压会变化，比如冬季胎压会降低）。</li></ul><h4 id="b.-%E5%85%B3%E6%B3%A8%E8%8A%B1%E7%BA%B9%22%E5%B1%80%E9%83%A8%E6%8D%9F%E4%BC%A4%22%EF%BC%9A%E6%AF%94%E6%B7%B1%E5%BA%A6%E6%9B%B4%E6%98%93%E8%A2%AB%E5%BF%BD%E8%A7%86" tabindex="-1">b. 关注花纹&quot;局部损伤&quot;：比深度更易被忽视</h4><p>除了深度，需定期观察胎面是否有以下问题（EV车型扭矩大，轮胎受力更集中，小损伤易扩大）：</p><ul><li>花纹沟里的&quot;石子&quot;：每周清理1次（用小钩子勾出），避免石子卡紧后扎穿胎面，或磨损花纹沟壁；</li><li>胎面&quot;裂纹/鼓包&quot;：若发现花纹沟有横向裂纹（老化导致），或胎侧有鼓包（过坑/过坎速度太快，内部帘线断裂），即使深度够也需更换（鼓包胎有爆胎风险，不能继续用）；</li><li>扎钉：若扎钉孔径≤6mm，可找专业门店补胎（用&quot;蘑菇钉&quot;补，比贴片更可靠）；若扎在胎侧或孔径太大，直接更换（EV车型高速行驶多，补胎后也建议放后轮用）。</li></ul><h4 id="c.-%E9%81%BF%E5%85%8D%22%E4%BC%A4%E8%83%8E%E8%A1%8C%E4%B8%BA%22%EF%BC%9A%E5%BB%B6%E9%95%BFev%E4%B8%93%E7%94%A8%E8%83%8E%E5%AF%BF%E5%91%BD" tabindex="-1">c. 避免&quot;伤胎行为&quot;：延长EV专用胎寿命</h4><p>米其林Pilot Sport EV的扁平比是40（低扁平比，胎侧薄、轮毂大），对使用场景更挑剔，需避免：</p><ul><li>过坑/过减速带：速度不超过20km/h，避免胎侧挤压变形导致鼓包（低扁平比轮胎的胎侧抗冲击能力弱）；</li><li>靠边停车：不要让轮胎蹭到路肩（易刮伤胎侧，导致帘线暴露）；</li><li>长时间停放：若超过1个月不用车，建议把车停在平整地面，避免轮胎单侧受力过久（导致局部变形）。</li></ul><h4 id="d.-%E5%90%8E%E7%BB%AD%E7%A3%A8%E6%8D%9F%E7%9B%91%E6%B5%8B%EF%BC%9A%E6%8E%A5%E8%BF%912.5mm%E6%97%B6%22%E6%8F%90%E5%89%8D%E8%AE%A1%E5%88%92%E6%9B%B4%E6%8D%A2%22" tabindex="-1">d. 后续磨损监测：接近2.5mm时&quot;提前计划更换&quot;</h4><p>虽然现在深度足够，但建议每3个月（或5000公里）复查一次花纹深度，尤其注意：</p><ul><li>若你经常跑高速、雨天通勤多：当深度接近2.5mm时，建议提前换胎——米其林Pilot Sport EV的花纹设计侧重湿地排水，深度不足会导致排水能力下降，雨天刹车距离变长；</li><li>更换建议：优先换同型号的米其林Pilot Sport EV（保持性能匹配），且<strong>前后轮需分别按原厂规格换</strong>（前轮245/40R20，后轮265/40R20），不能混用其他品牌或规格的轮胎。</li></ul><div style="margin-bottom: 40px;"></div><h2 id="%E4%BA%8C%E3%80%814%E4%B8%87%E5%85%AC%E9%87%8C%E5%91%A8%E6%9C%9F%E4%BF%9D%E5%85%BB%E8%AE%B0%E5%BD%95" tabindex="-1">二、4万公里周期保养记录</h2><h3 id="1.-%E8%BD%AE%E8%83%8E%E8%8A%B1%E7%BA%B9%E6%B7%B1%E5%BA%A6%E6%A3%80%E6%9F%A5" tabindex="-1">1. 轮胎花纹深度检查</h3><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/tyre-check-1.jpg" style="width: 40%; display: inline-block; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/tyre-check-2.jpg" style="width: 40%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/tyre-check-3.jpg" style="width: 40%; display: inline-block; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/tyre-check-4.jpg" style="width: 40%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" > <h3 id="2.-%E5%BA%95%E7%9B%98%E6%A3%80%E6%9F%A5" tabindex="-1">2. 底盘检查</h3><div style="display: flex; gap: 10px; margin-bottom: 10px;">  <div style="width: 58%; display: flex; flex-direction: column; gap: 10px;">    <img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/bottom-dmage.jpg" />  </div>  <div style="width: 58%; display: flex; flex-direction: column; gap: 10px;">    <img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/bottom-check-1.jpg" style="width: 100%; object-fit: cover; height: auto;" />    <img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/bottom-check-2.jpg" style="width: 100%; object-fit: cover; height: auto;" />    <img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/bottom-check-3.jpg" style="width: 100%; object-fit: cover; height: auto;" />  </div></div><h3 id="3.-%E4%BF%9D%E9%99%A9%E6%9D%A0%E7%9F%B3%E5%AD%90%E7%A3%95%E7%A2%B0%E4%BF%AE%E5%A4%8D%EF%BC%8C%E9%A1%BA%E4%BE%BF%E6%B8%85%E7%90%86%E4%BA%86%E4%B8%80%E4%B8%8B%E5%86%B7%E5%87%9D%E5%99%A8%E5%A0%86%E7%A7%AF%E7%9A%84%E8%84%8F%E4%B8%9C%E8%A5%BF" tabindex="-1">3. 保险杠石子磕碰修复，顺便清理了一下冷凝器堆积的脏东西</h3><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/front-clean-1.jpg" style="width: 40%; display: inline-block; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/front-clean-3.jpg" style="width: 40%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/front-clean-2.jpg" style="width: 40%; display: inline-block; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/front-clean-4.jpg" style="width: 40%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" > <img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/40k-check-record/front-clean-5.jpg" style="width: 83%;" /><h3 id="4.-%E5%85%B6%E4%BB%96%E6%9B%B4%E6%8D%A2%E9%83%A8%E4%BB%B6%E5%8C%85%E6%8B%AC%EF%BC%9A%E9%9B%A8%E5%88%AE%E5%99%A8%EF%BC%8C%E7%A9%BA%E8%B0%83%E6%B5%81%E7%95%85%E6%80%A7%EF%BC%8C%E5%88%B9%E8%BD%A6%E6%B2%B9%EF%BC%88%E6%97%A7%E7%89%88%E6%9C%8D%E5%8A%A14%E4%B8%87%E5%85%AC%E9%87%8C%E8%B5%A0%E9%80%81%EF%BC%89" tabindex="-1">4. 其他更换部件包括：雨刮器，空调流畅性，刹车油（旧版服务4万公里赠送）</h3><div style="margin-bottom: 40px;"></div><h2 id="%E4%B8%89%E3%80%81%E5%85%B3%E9%94%AE%E9%83%A8%E4%BB%B6%E6%9B%B4%E6%8D%A2%E4%B8%8E%E6%A3%80%E6%9F%A5%E6%A1%A3%E6%A1%88" tabindex="-1">三、关键部件更换与检查档案</h2><p>此部分为个人记录，用于长期追踪部件状态，为更换决策提供数据支持。</p><h3 id="1.-%E8%BD%AE%E8%83%8E%E6%9B%B4%E6%8D%A2%E5%8E%86%E5%8F%B2%E4%BB%B7%E6%A0%BC%E5%8F%82%E8%80%83-(618%E6%B4%BB%E5%8A%A8)" tabindex="-1">1. 轮胎更换历史价格参考 (618活动)</h3><ul><li>前胎规格：米其林 <strong>245/40R20 99Y PILOT SPORT EV</strong></li><li>后胎规格：米其林 <strong>265/40R20 104Y PILOT SPORT EV</strong></li><li>价格记录：</li></ul><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/new-tyre-1.png" style="width: 30%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/new-tyre-2.png" style="width: 30%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/new-tyre-3.png" style="width: 30%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" ><h3 id="2.-%E5%88%B9%E8%BD%A6%E7%89%87%E7%A3%A8%E6%8D%9F%E6%A3%80%E6%9F%A5%E8%AE%B0%E5%BD%95" tabindex="-1">2. 刹车片磨损检查记录</h3><ul><li>记录时间：2025/07/10</li><li>检查方式：使用刹车片测量尺即可，后期可对照记录照片里的方式自行检查。</li><li>检查记录：</li></ul><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/check-brake-pad-1.jpg" style="width: 40%; display: inline-block; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/check-brake-pad-2.jpg" style="width: 40%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/check-brake-pad-3.jpg" style="width: 40%; display: inline-block; margin-bottom: 10px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/check-brake-pad-4.jpg" style="width: 40%; display: inline-block; margin-left: 10px; margin-bottom: 10px;" >  <h3 id="3.-%E8%A1%A5%E8%83%8E%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95" tabindex="-1">3. 补胎历史记录</h3><ul><li>目的：方便快速查找历史维修记录，评估轮胎状况。</li><li>维修记录：</li></ul><h4 id="%E5%B7%A6%E5%90%8E%E8%83%8E%E7%AC%AC%E4%B8%80%E6%AC%A1" tabindex="-1">左后胎第一次</h4><div style="display: flex; gap: 10px; margin-bottom: 10px;">  <video src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/fix-tyre-1.mp4" controls="controls" style="width: 61.5%;">    您的浏览器不支持 video 标签。  </video>  <div style="display: flex; flex-direction: column; gap: 10px;">    <img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/fix-tyre-1-1.jpg" style="width: 100%; object-fit: cover; height: auto;">    <img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/fix-tyre-1-2.jpg" style="width: 100%; object-fit: cover; height: auto;">    <img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/fix-tyre-1-3.jpg" style="width: 100%; object-fit: cover; height: auto;">  </div></div><h4 id="%E5%B7%A6%E5%90%8E%E8%83%8E%E7%AC%AC%E4%BA%8C%E6%AC%A1" tabindex="-1">左后胎第二次</h4><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-14-su7-maintain/history-record/fix-tyre-2-1.jpg" style="width: 45%;" >  ]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2023 新家装修记录]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2023-room-tour" />
                <id>tag:https://fx67ll.xyz,2025-08-11:2023-room-tour</id>
                <published>2025-08-11T23:02:01+08:00</published>
                <updated>2025-09-29T10:34:24+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h1 id="2023-%E6%96%B0%E5%AE%B6%E8%A3%85%E4%BF%AE%E8%AE%B0%E5%BD%95" tabindex="-1">2023 新家装修记录</h1><p>之前在微信的收藏里记录了自己的新家装修记录照片，现在转移到自己的站点永久保存记录一下，也许多少年之后回首看看也很有意思~</p><p>说下我家的基本情况吧。首先是交付就会有一部分精装修，厨房和卫生间、加上大部分肉眼可见的、比如地板墙面的部分都是开发商装好的，所以我家剩余的装修内容只有 <code>柜子+生活家具+电器设备</code>~</p><p>因为大概率是这辈子，第一次，也是最后一次设计自己生活的房子，所以也是非常上心的做了很多巧思，不过细节就不放出来了，这个帖子就单纯做个记录吧，希望能顺便也给大家做个参考，如果有帮助就更好了~</p><h4 id="%E8%AE%BE%E8%AE%A1%E5%9B%BEppt" tabindex="-1">设计图PPT</h4><p><a href="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/fx67ll-room-design.pdf" target="_blank">附件（chorme高版本可以直接打开在线预览）</a></p><h2 id="%E5%A4%A7%E4%BB%B6%E5%AE%B6%E5%85%B7" tabindex="-1">大件家具</h2><ul><li>客厅<ul><li>电视柜</li><li>沙发</li><li>餐边柜</li><li>餐桌</li><li>餐椅</li><li>客用凳</li><li>地垫</li><li>窗帘</li></ul></li><li>主卧<ul><li>衣柜</li><li>床头柜</li><li>床架</li><li>床垫</li><li>高矮斗柜</li><li>梳妆台</li><li>地垫</li><li>窗帘</li><li>纱窗</li></ul></li><li>次卧<ul><li>衣柜</li><li>榻榻米</li><li>床垫</li><li>床头柜</li><li>落地全身镜</li><li>书桌</li><li>地垫</li><li>窗帘</li><li>纱窗</li></ul></li><li>书房<ul><li>上床下桌</li><li>床垫</li><li>地垫</li><li>小边柜</li><li>窗帘</li><li>纱窗</li></ul></li><li>厨房<ul><li>百叶窗</li><li>纱窗</li></ul></li><li>卫生间<ul><li>百叶窗</li><li>纱窗</li><li>吸水地垫</li></ul></li><li>阳台<ul><li>小沙发</li><li>纱窗</li></ul></li></ul><h2 id="%E7%94%B5%E5%99%A8%E8%AE%BE%E5%A4%87" tabindex="-1">电器设备</h2><ul><li>网线、路由器、小爱同学</li><li>两室一厅的电动窗帘</li><li>厨房冰箱、洗碗机、净水器、管线机</li><li>三室一厅、过道的灯</li><li>三室一厅、厨房、卫生间、阳台的开关</li><li>厨房、卫生间的水龙头</li><li>客厅电视、电视音响</li></ul><h2 id="%E6%9C%AA%E5%85%A5%E4%BD%8F%E6%97%B6%E7%9A%84%E8%AE%B0%E5%BD%95" tabindex="-1">未入住时的记录</h2><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/1-keting.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/2-keting.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/3-keting.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/4-keting.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/5-keting.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/6-ciwo.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/7-ciwo.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/8-zhuwo.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/9-zhuwo.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/10-shufang.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/11-shufang.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/12-yangtai.jpg" style="margin-bottom: 20px;" ><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/13-yangtai.jpg" style="margin-bottom: 20px;" ><h2 id="%E4%B8%80%E4%BA%9B%E5%AE%9A%E5%88%B6%E5%9E%AB%E5%AD%90%E7%9A%84%E5%B0%BA%E5%AF%B8%E4%BF%A1%E6%81%AF" tabindex="-1">一些定制垫子的尺寸信息</h2><h4 id="%E5%AE%A2%E5%8E%85%E5%9C%B0%E5%9E%AB%E5%B0%BA%E5%AF%B8%E4%BF%A1%E6%81%AF%E5%AD%98%E6%A1%A3" tabindex="-1">客厅地垫尺寸信息存档</h4><p><code>315cm * 270cm + 37cm * 200cm</code><br /><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/14-ketingdidian.jpg" style="margin-top: 20px;" ></p><h4 id="%E4%B8%BB%E5%8D%A7%E9%A3%98%E7%AA%97%E5%B0%BA%E5%AF%B8%E4%BF%A1%E6%81%AF%E5%AD%98%E6%A1%A3" tabindex="-1">主卧飘窗尺寸信息存档</h4><p><code>231cm * 72cm（凸出来：4cm*1cm）</code><br /><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/15-zhuwopiaochuangdian.jpg" style="margin-top: 20px;" ></p><h4 id="%E6%AC%A1%E5%8D%A7%E6%A6%BB%E6%A6%BB%E7%B1%B3%E5%B0%BA%E5%AF%B8%E4%BF%A1%E6%81%AF" tabindex="-1">次卧榻榻米尺寸信息</h4><p><code>主体 —— 215cm * 181cm</code><br /><code>上方书桌下方 —— 118.5cm * 29cm</code><br /><code>左侧窗帘下方 —— 198.5cm * 10cm</code><br /><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/16-ciwochuangdian.jpg" style="margin-top: 20px;" ></p><h4 id="%E4%B9%A6%E6%88%BF%E5%9C%B0%E5%9E%AB%E5%B0%BA%E5%AF%B8%E4%BF%A1%E6%81%AF" tabindex="-1">书房地垫尺寸信息</h4><p><code>194cm * 148cm（凹进来：60cm*60cm）</code><br /><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/17-shufangdidian.jpg" style="margin-top: 20px;" ></p><h2 id="%E4%BA%A4%E6%88%BF%E9%AA%8C%E6%94%B6%E8%A1%A8" tabindex="-1">交房验收表</h2><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-11-room-tour/18-yanshoubiao.jpg" >]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[github commits 2018 - 2025]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/github-commits-2018-2025" />
                <id>tag:https://fx67ll.xyz,2025-08-06:github-commits-2018-2025</id>
                <published>2025-08-06T23:10:44+08:00</published>
                <updated>2025-08-12T13:59:37+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>2021年的巅峰可能再也无法复现，但是2025年的下半程</p><p>还是有机会去追赶，不忘初心，切勿沉沦，Stay Hungry Stay Foolish！</p><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-06-github-commits/2018.png"><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-06-github-commits/2019.png"><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-06-github-commits/2020.png"><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-06-github-commits/2021.png"><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-06-github-commits/2022.png"><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-06-github-commits/2023.png"><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-06-github-commits/2024.png"><img src="https://test.fx67ll.com/fx67ll.xyz-blog/2025-08-06-github-commits/2025.png" style="margin-bottom: 40px;"><p>“Success is not final, Failure is not fatal. It is the courage to continue that counts.” --Winston Churchill.</p><p>成功不要紧，失败不致命。拥有继续前行的勇气，才最可贵！</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2025 直面内心恐惧]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2025-half-record" />
                <id>tag:https://fx67ll.xyz,2025-08-01:2025-half-record</id>
                <published>2025-08-01T10:02:14+08:00</published>
                <updated>2025-08-12T00:51:16+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>2025年中已过，吐槽吐槽近期我的蕉绿，来给未来的自己留一个日志记录~</p><p>如果你看到这篇文章，请忽略我的这些废话~</p><p>先放上史上最强创业团队的图镇楼！</p><p><img src="/upload/2024/12/%E5%8F%B2%E4%B8%8A%E6%9C%80%E5%BC%BA%E5%88%9B%E4%B8%9A%E5%9B%A2%E9%98%9F.png" alt="史上最强创业团队" /></p><h2 id="%E5%B1%B1%E7%A9%B7%E6%B0%B4%E5%B0%BD%E5%B9%B6%E4%B8%8D%E7%AE%97%E4%BB%80%E4%B9%88" tabindex="-1">山穷水尽并不算什么</h2><p>从做出选择的那一刻开始，我就应该明白，我需要直面恐惧，并保持好心态</p><p>要知道，居安思危和保持好平常心并不冲突，如果某一方在内心的平衡中占比过多，都会给我埋下各种雷</p><p>我一直在害怕失业，这是理所应当的。同时，我也应该明白，就算山穷水尽，我也可以去干另一个我喜欢的事，比如开车：我喜欢安全快速地运送乘客（我是坚定的电车党）</p><p>当然码农一定是我的第一选择，因为编码的过程是一种真正创造价值的过程，能让我获得小小的成就感，感受到自己存在于社会的价值</p><p>所以，今天突然想明白一个道理，就算没人愿意雇佣我去做码农，我也可以去一边靠别的工作维生，一边继续学习编码，练习编码，等待继续参与码农工作的机会不是吗?</p><p><img src="/upload/2023/06/%E4%BD%A0%E6%89%93%E7%AE%97%E8%B7%B5%E8%B8%8F%E4%B8%80%E4%B8%AA%E7%94%B7%E4%BA%BA%E7%9A%84%E8%A7%89%E6%82%9F%E5%90%97.png" alt="你打算践踏一个男人的觉悟吗" /></p><p><img src="/upload/2023/06/%E6%AD%BB%E4%BA%86%E7%9A%84%E8%AF%9D%E5%B0%B1%E8%AF%B4%E6%98%8E%E6%88%91%E5%8F%AA%E6%98%AF%E8%BF%99%E7%A7%8D%E7%A8%8B%E5%BA%A6%E7%9A%84%E7%94%B7%E4%BA%BA.JPG" alt="死了的话就说明我只是这种程度的男人" /></p><h2 id="%E5%A4%A7%E7%8E%AF%E5%A2%83" tabindex="-1">大环境</h2><p>21年，我在房价的最高点上车了，距离我还完自己背的债还剩26年，这是我当前的压力恐惧源泉</p><p>蜡笔小新中的广志也是在日本泡沫时期之前买了房，动画中他常常在地铁上打瞌睡，疲惫不堪，但是他的一家依然是充满欢乐，<a href="https://love.fx67ll.com" target="_blank">广志也非常爱美冴</a>，他们的故事也是我的精神动力</p><p>在大环境变差之前我一直是乐观派，我致力于提升自己的技术水平，但是我忽略了技术永远是服务于需求的，没有需求的话，再顶尖的技术也会被封入宝箱</p><p>所以除了提升技术，还需要扩大自己的视野，去敏锐的和时代一起升级，活在自己的舒适区，一定是最危险的</p><p><img src="/upload/2025/08/%E5%8F%AF%E6%98%AF%E6%88%91%E8%BF%98%E6%9C%8932%E5%B9%B4%E8%B4%B7%E6%AC%BE%E6%B2%A1%E8%BF%98%E5%AE%8C%E5%93%8E.jpeg" alt="可是我还有32年贷款没还完哎" /></p><p><img src="/upload/2025/08/%E9%81%87%E8%A7%81%E7%BE%8E%E5%86%B4.jpg" alt="遇见美冴" /></p><p><img src="/upload/2025/08/%E7%88%B8%E7%88%B8%E4%BC%9A%E5%8A%A0%E6%B2%B9%E7%9A%84.jpg" alt="爸爸会加油的" /></p><h2 id="%E4%B8%8D%E8%A6%81%E5%BF%98%E8%AE%B0%E8%87%AA%E5%B7%B1%E7%9A%84%E5%88%9D%E5%BF%83" tabindex="-1">不要忘记自己的初心</h2><p>从吃屎狗和OW的历史来看，心态一定是做好一件事最重要的基础，然后就是长期的练习，不断学习进步适应变化的环境</p><p>过往的成就或者经验，一旦放弃，重新拾起，可能要花费成百上千倍的努力，因为你需要重新找到适应的方式，重新适应环境，习惯一旦丢弃，再次养成的代价很高</p><p><img src="/upload/2023/06/%E8%B7%AF%E9%A3%9E%E8%A2%AB%E7%88%B7%E7%88%B7%E6%95%B2%E5%A4%B4.jpg" alt="路飞被爷爷敲头" /></p><h2 id="%E7%94%A8%E6%97%B6%E9%97%B4%E6%8D%A2%E7%A9%BA%E9%97%B4" tabindex="-1">用时间换空间</h2><p>未来的大方向，一定是熟练使用AI进行人机协作编码的工程师才能够生存下来，在AI大爆发的当下，我需要进一步考虑转型的方向</p><p>过往很多工作中的经验，都是在不断踩坑中学会的，现在AI可以精确地帮助你避坑并趋近完美的解决问题，也许未来工作年限不再是一种优势</p><p>我现在对自己的定位，就是一个全栈业务开发工程师，我如何去接触AI模型这个赛道，我问了问deepseek</p><h3 id="ai%E5%AF%B9%E6%88%91%E7%9A%84%E5%BB%BA%E8%AE%AE" tabindex="-1">AI对我的建议</h3><p>在AI时代，随着零代码/低代码工具（如tare）的普及，开发工程师面临的挑战既是压力也是机遇。关键在于如何重新定位自身价值，适应技术变革。以下是一些应对策略：</p><h4 id="1.-%E4%BB%8E%22%E7%BC%96%E7%A0%81%E6%89%A7%E8%A1%8C%E8%80%85%22%E8%BD%AC%E5%90%91%22%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84%E5%B8%88%22" tabindex="-1">1. <strong>从&quot;编码执行者&quot;转向&quot;技术架构师&quot;</strong></h4><ul><li><strong>聚焦复杂系统设计</strong>：零代码工具擅长标准化场景，但复杂业务逻辑、高并发系统、安全架构等仍需专业工程师设计。</li><li><strong>成为AI工具的&quot;指挥者&quot;</strong>：通过API集成、数据流设计、分布式架构等，将AI生成的基础模块组合成完整解决方案。</li><li><strong>掌握混合开发模式</strong>：将低代码平台与定制开发结合，在提升效率的同时保留技术深度。</li></ul><h4 id="2.-%E5%BC%BA%E5%8C%96ai%E6%97%B6%E4%BB%A3%E7%9A%84%E6%A0%B8%E5%BF%83%E7%AB%9E%E4%BA%89%E5%8A%9B" tabindex="-1">2. <strong>强化AI时代的核心竞争力</strong></h4><ul><li><strong>技术深度升级</strong>：<ul><li>掌握AI工程化能力（模型微调、Prompt工程、AI应用开发）</li><li>深入底层原理（分布式系统、算法优化、硬件加速）</li><li>研究新兴领域（边缘计算、量子计算、隐私计算）</li></ul></li><li><strong>业务理解深化</strong>：<ul><li>成为垂直领域专家（医疗、金融、工业等）</li><li>培养产品思维和用户体验设计能力</li><li>掌握数据驱动决策方法（AB测试、数据分析）</li></ul></li></ul><h4 id="3.-%E6%9E%84%E5%BB%BA%E4%BA%BA%E6%9C%BA%E5%8D%8F%E4%BD%9C%E6%96%B0%E8%8C%83%E5%BC%8F" tabindex="-1">3. <strong>构建人机协作新范式</strong></h4><ul><li><strong>AI增强开发流程</strong>：<ul><li>用AI工具完成重复性工作（代码生成、测试用例编写）</li><li>专注创造性任务（架构设计、性能优化）</li><li>建立AI辅助的质量保障体系（智能测试、异常检测）</li></ul></li><li><strong>开发范式进化</strong>：<ul><li>从&quot;写代码&quot;转向&quot;训练AI助手&quot;</li><li>从功能实现转向价值创造</li><li>从单兵作战转向人机协作团队管理</li></ul></li></ul><h4 id="4.-%E6%8B%93%E5%B1%95%E6%8A%80%E6%9C%AF%E8%BE%B9%E7%95%8C" tabindex="-1">4. <strong>拓展技术边界</strong></h4><ul><li><strong>向技术栈上游迁移</strong>：<ul><li>参与基础工具链开发（改进低代码平台本身）</li><li>开发领域特定语言（DSL）或可视化编程工具</li><li>构建AI开发基础设施（模型服务化、持续训练平台）</li></ul></li><li><strong>向下游价值延伸</strong>：<ul><li>提供技术咨询服务（企业数字化改造）</li><li>打造行业解决方案（结合AI+低代码的垂直应用）</li><li>创建技术生态（插件市场、模板库、培训体系）</li></ul></li></ul><h4 id="5.-%E5%9F%B9%E5%85%BB%E4%B8%8D%E5%8F%AF%E6%9B%BF%E4%BB%A3%E7%9A%84%22%E4%BA%BA%E7%B1%BB%E4%BC%98%E5%8A%BF%22" tabindex="-1">5. <strong>培养不可替代的&quot;人类优势&quot;</strong></h4><ul><li><strong>创新与批判思维</strong>：<ul><li>突破AI的范式局限</li><li>设计颠覆性解决方案</li><li>进行技术伦理评估</li></ul></li><li><strong>跨领域整合能力</strong>：<ul><li>融合多技术栈（AI+IoT+区块链）</li><li>连接技术与商业价值</li><li>协调人机协作流程</li></ul></li><li><strong>复杂决策能力</strong>：<ul><li>处理模糊需求</li><li>平衡技术债务</li><li>管理不确定风险</li></ul></li></ul><h4 id="6.-%E5%BB%BA%E7%AB%8B%E6%8C%81%E7%BB%AD%E8%BF%9B%E5%8C%96%E6%9C%BA%E5%88%B6" tabindex="-1">6. <strong>建立持续进化机制</strong></h4><ul><li><strong>动态学习体系</strong>：<ul><li>追踪Gartner技术成熟度曲线</li><li>参与开源社区建设</li><li>构建个人知识图谱</li></ul></li><li><strong>实践验证循环</strong>：<ul><li>定期进行技术沙盘推演</li><li>开展概念验证（PoC）项目</li><li>建立技术雷达评估机制</li></ul></li></ul><h4 id="%E8%A1%8C%E4%B8%9A%E8%B6%8B%E5%8A%BF%E5%BA%94%E5%AF%B9%E7%AD%96%E7%95%A5%EF%BC%9A" tabindex="-1">行业趋势应对策略：</h4><ul><li><strong>短期（1-3年）</strong>：成为AI工具的超级用户，用自动化工具提升10倍效率</li><li><strong>中期（3-5年）</strong>：向解决方案架构师转型，主导AI原生系统的设计</li><li><strong>长期（5-10年）</strong>：向技术战略家进化，推动行业技术范式变革</li></ul><p>开发者真正的危机不在于工具进化，而在于思维停滞。未来的顶尖工程师将是那些能站在AI肩膀上，用技术解决人类级复杂问题的&quot;技术哲学家&quot;。保持对本质问题的思考（如：用户到底需要什么？技术如何创造真实价值？），才能在技术浪潮中立于不败之地。</p><p><img src="/upload/2025/01/2025%E5%AF%B9%E8%87%AA%E5%B7%B1%E7%9A%84%E5%B1%95%E6%9C%9B.jpg" alt="2025对自己的展望" /></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2025 冷静下来]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2025-clam-down" />
                <id>tag:https://fx67ll.xyz,2025-07-15:2025-clam-down</id>
                <published>2025-07-15T04:04:04+08:00</published>
                <updated>2026-01-23T09:20:57+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E4%B8%BA%E4%BB%80%E4%B9%88%E6%9C%89%E4%BA%9B%E7%94%B5%E5%AD%90%E9%98%B3%E7%97%BF" tabindex="-1">为什么有些电子阳痿</h3><p>截至今年5月28日，我已正式步入职场7年，到今天（2025-07-15）已经2605天，还有一年多的时间我就要30岁了</p><p>回想起之前全身心沉浸入CSGO的体验，似乎至今都再也没有发生过，大体是因为我不再是一个人，不需要通过获胜的快感弥补孤独的恐惧</p><p>前一阵子，因为恐惧失业和一些个人问题的原因，又把自己关进了一些竞技游戏的牢笼，但是最终我还是明白，我不需要那些段位和勋章来证明自己</p><p>大概这就是为什么我电子阳痿了</p><h3 id="%E4%B9%9F%E8%AE%B8%E8%87%AA%E5%BE%8B%E6%89%8D%E8%83%BD%E8%AE%A9%E8%BF%99%E4%B8%AA%E5%B9%B4%E7%BA%AA%E7%9A%84%E6%88%91%E6%88%90%E9%95%BF%E4%B8%80%E4%BA%9B" tabindex="-1">也许自律才能让这个年纪的我成长一些</h3><p>每天三十分钟强制运动</p><p>强制自己定时把家里收拾好</p><p>每天获得一些专业技能成长</p><h3 id="%E5%9C%A8%E6%B5%A9%E9%B2%B8%E6%94%B6%E8%8E%B7%E4%BA%86%E4%BB%80%E4%B9%88%EF%BC%9F" tabindex="-1">在浩鲸收获了什么？</h3><p>没了小公司里面的一个人承担所有的激情，沦为了处理工单的无情机器</p><p>有些时候甚至不知道做这个功能的意义，只是看着方案码代码</p><p>以前每解决一个难题都很开心，接到一句话的需求都会说，可以搞起来，边搞边研究</p><p>现在方案里一点点写的不合理的地方，都要生气去找人理论</p><p>我是不是已经忘记了自己从前的初心？Coding is my life.</p><h3 id="%E4%BA%BA%E6%98%AF%E4%BC%9A%E5%8F%98%E7%9A%84" tabindex="-1">人是会变的</h3><p>卢本伟以前就说过，人是会变的，我不相信，并表示轻蔑</p><p>现在看来，似乎他对了一半，人是会变的，但人也是会自省的</p><p>吾日三省吾身，能把自己逐渐偏离的初心，再一点一点拽回来</p><h4 id="%E4%BA%BA%E7%94%9F%E7%9C%9F%E7%9A%84%E5%BE%88%E7%9F%AD%EF%BC%8C%E6%8A%8A%E6%8F%A1%E5%BD%93%E4%B8%8B%E6%8B%A5%E6%9C%89%E7%9A%84%E5%B9%B8%E7%A6%8F%EF%BC%8C%E6%AF%94%E4%BB%80%E4%B9%88%E9%83%BD%E8%A6%81%E9%87%8D%E8%A6%81" tabindex="-1">人生真的很短，把握当下拥有的幸福，比什么都要重要</h4>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2025 精神力]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2025-spirit" />
                <id>tag:https://fx67ll.xyz,2025-02-25:2025-spirit</id>
                <published>2025-02-25T04:04:04+08:00</published>
                <updated>2026-01-23T09:20:50+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E9%87%8D%E6%96%B0%E6%89%BE%E5%9B%9E%E8%87%AA%E6%88%91%E6%80%BB%E7%BB%93%E5%92%8C%E5%8F%8D%E7%9C%81" tabindex="-1">重新找回自我总结和反省</h3><p>很久没有写自己的个人总结日志，主要是这两年一直都很充实，很少有时间静下心来聊聊。</p><p>首先是工作上安排的满满当当，被裁员的达摩克利斯之剑悬顶，我一直试图小心翼翼的做好每一件事，想要在公司让大家看到我的价值，不过也正是全身心投入到了公司的事业中，个人的技术成长之路，被放在了一边，可以说是止步不前，这对于我来说是非常大的危机！</p><p>脱离了公司的温室，可能我会瞬间就枯萎在行业的土壤之中，更何况经济下行，水土流失及其严重，我真的需要好好反思！</p><p>其次就是生活上各类琐碎的杂事，基本每个周末都会被各种杂事安排的满满当当。</p><p>不过也是在2024年，我完成了定制家装、买家具、搬家、以及给楠宝买车这三件大事，我的生活也进入了稳定还债的轨道。</p><p>说到债务，必须要说一嘴，这是个让我在最穷苦的日子获得了宝贵的物质奖励，却也让我做出了很多错误抉择的双刃剑，记得小时候我接触的第一款游戏是QQ三国，当时还没有氪金这个词，那个时候我便习惯了先屯很多很多的药品和装备，再去地图里冒险闯荡，而我工作之后的日子，也和这个游戏一样，为了屯粮不断花费了很多马内，有些回头来看完全没有必要，也算是年轻的代价。</p><p>当然，很多人也会说自己被房贷车贷所束缚手脚，但是在我看来，合适的房贷和车贷，能够给自己适当的压力，让自己不断思考出路，特别是像我这种，没有背景，也没有家族财富继承，靠着自己的双手努力养活自己的普通的人，债务压力是不断逼迫自己前进的最强动力，希望我能挺过这最难的最后一关吧。</p><h3 id="%E8%AF%B4%E8%AF%B42025%E5%B9%B4%E7%9A%84%E4%B8%80%E4%BA%9B%E5%8F%98%E5%8C%96" tabindex="-1">说说2025年的一些变化</h3><p>工作上最主要的变化就是：我的老领导被调到了AI团队，公司转入了AI优先的战略。可是我却没有一点能够参与AI时代的技能，我不得不反问自己，我是不是落后了？</p><p>落后就要挨打，不论在什么时候，这句话都非常适用，为了我的职业生涯，也为了我爱的人，我需要继续学习，在技术的道路上坚持下去！</p><p>最后，我必须要警醒自己：无论如何都要尽全力完成工作，好好工作是稳定还债的基石，切不可放弃居安思危的态度！</p><p>关于生活上，其实没有很巨大的变化，但是我已经在做出一些改变，比如尽可能减少不必要的开支，我希望这两年，能够苟住并尽全力创收，如果真到了最后一步，好好和家人沟通，一些敏感的数字我都详细记录了用途。</p><h3 id="%E6%94%B6%E5%B0%BE" tabindex="-1">收尾</h3><p>本来是想写一篇开放式的博文，但写着写着还是暴露了很多自己的隐私，所以还是放弃了，所有的所有，都是为了提醒自己：不忘初心，方得始终。</p><p>也许，努力并不会取得成功，但是坚持自己所爱的道路，坚持走下去，也许在某一个时刻，会有一丝光亮温暖你，你说是吧？</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[用bat脚本批量整理文件夹]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/bat-file" />
                <id>tag:https://fx67ll.xyz,2024-08-12:bat-file</id>
                <published>2024-08-12T09:29:43+08:00</published>
                <updated>2025-08-12T14:36:38+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>最近硬盘存储的照片和视频素材满了，需要整理下数据，然后备份存档。</p><p>期间发现有很多视频文件名称有点问题，手动改费时费力。</p><p>于是了解了下如何批量自动修改文件名或者文件夹名称，真是方便快捷。</p><p>下方放出一个示例，供自己以后继续参考~</p><pre><code class="language-">@echo offsetlocal enabledelayedexpansion:: 遍历文件夹中的所有文件for %%f in (*) do (    :: 检查文件名中是否包含_2024    if /i &quot;%%f&quot; neq &quot;%%~nf&quot; (        :: 去除_2024        set &quot;newname=%%~nf&quot;        set &quot;newname=!newname:_2024=!&quot;        :: 如果文件名中还有空格，需要处理一下，否则重命名会失败        set &quot;newname=!newname: =_!&quot;        :: 重命名文件        ren &quot;%%f&quot; &quot;!newname!%%~xf&quot;    ))echo Done.pause</code></pre>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[简述 JS 中对象的创建和拷贝 ]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/js-object-create-copy" />
                <id>tag:https://fx67ll.xyz,2024-07-09:js-object-create-copy</id>
                <published>2024-07-09T11:10:58+08:00</published>
                <updated>2025-07-02T11:25:44+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>在 JavaScript 中，对象是一种非常重要且灵活的数据结构，用于存储多个值（属性）和方法（函数）</p><p>对象的创建和拷贝是日常开发中经常涉及的操作，对于业务逻辑的准确实现有着重要的作用</p><p>本文将简要概括 JavaScript 中对象的创建和拷贝方式，都是一些非常基础的知识，大家看个乐就好~</p><h2 id="%E7%9B%AE%E5%BD%95" tabindex="-1">目录</h2><ul><li><a href="#%E5%AF%B9%E8%B1%A1%E7%9A%84%E4%BD%9C%E7%94%A8">对象的作用</a></li><li><a href="#%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1">创建对象</a><ul><li><a href="#%E5%AF%B9%E8%B1%A1%E5%AD%97%E9%9D%A2%E9%87%8F">对象字面量</a></li><li><a href="#%E4%BD%BF%E7%94%A8-new-object-%E6%96%B9%E6%B3%95">使用 <code>new Object()</code> 方法</a></li><li><a href="#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">构造函数</a></li><li><a href="#objectcreate">Object.create()</a></li><li><a href="#%E7%B1%BBes6">类（ES6+）</a></li></ul></li><li><a href="#%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%8B%B7%E8%B4%9D">对象的拷贝</a><ul><li><a href="#%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D%E7%9A%84%E5%8C%BA%E5%88%AB">深拷贝与浅拷贝的区别</a></li><li><a href="#%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E6%B7%B1%E6%8B%B7%E8%B4%9D">如何实现深拷贝</a></li><li><a href="#%E5%B1%95%E5%BC%80%E8%BF%90%E7%AE%97%E7%AC%A6%E6%98%AF%E6%B7%B1%E6%8B%B7%E8%B4%9D%E8%BF%98%E6%98%AF%E6%B5%85%E6%8B%B7%E8%B4%9D">展开运算符是深拷贝还是浅拷贝</a></li></ul></li><li><a href="#%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86%E7%82%B9js-%E4%B8%AD%E6%9C%89%E5%93%AA%E4%BA%9B%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B">补充知识点：JS 中有哪些数据类型？</a></li><li><a href="#%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E5%90%88%E9%9B%86">面试问题合集</a></li></ul><h2 id="%E5%AF%B9%E8%B1%A1%E7%9A%84%E4%BD%9C%E7%94%A8" tabindex="-1">对象的作用</h2><p>JavaScript中的对象是一种复合数据类型，用于存储多个值（属性）和方法（函数）。它主要有以下作用：</p><ul><li><strong>组织数据</strong>：对象可以存储和管理相关数据，使数据处理更加结构化。</li><li><strong>封装功能</strong>：对象可以包含相关的函数，便于管理和使用。</li><li><strong>数据抽象</strong>：对象允许隐藏内部实现细节，只暴露必要的操作接口。</li><li><strong>模拟现实世界实体</strong>：对象是现实世界实体的良好抽象，有助于在程序中模拟现实世界的行为和属性。</li></ul><h2 id="%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1" tabindex="-1">创建对象</h2><p>在JavaScript中，创建对象是非常常见的操作，因为对象是一种非常灵活的数据结构，用于存储和组织数据。以下是创建对象的几种主要方式及其特点：</p><h3 id="1.-%E5%AF%B9%E8%B1%A1%E5%AD%97%E9%9D%A2%E9%87%8F" tabindex="-1">1. 对象字面量</h3><p>这是创建对象最简单也是最直接的方式。通过大括号 <code>{}</code> 来定义一个新对象，可以在其中直接定义属性和方法。</p><pre><code class="language-javascript">let person = {  name: &quot;Alice&quot;,  age: 25,  greet: function() {    console.log(&quot;Hello, &quot; + this.name);  }};</code></pre><p><strong>优点</strong>：简单直观，易于阅读和写入。<br /><strong>缺点</strong>：不适合创建具有相同属性和方法的多个实例。</p><h3 id="2.-%E4%BD%BF%E7%94%A8-new-object()-%E6%96%B9%E6%B3%95" tabindex="-1">2. 使用 <code>new Object()</code> 方法</h3><p>这种方法使用 <code>new Object()</code> 构造函数来创建一个新的空对象。这是一种较为基础的方法，与对象字面量 <code>{}</code> 类似，但使用了构造函数的形式。</p><pre><code class="language-javascript">let person = new Object();person.name = &quot;Eve&quot;;person.age = 28;person.greet = function() {  console.log(&quot;Hello, &quot; + this.name);};</code></pre><p><strong>优点</strong>：</p><ul><li>明确表达了创建对象的意图。</li><li>可以动态添加属性和方法。</li></ul><p><strong>缺点</strong>：</p><ul><li>与使用对象字面量相比，没有明显的优势，而且写法更繁琐。</li><li>每次使用 <code>new Object()</code> 都会创建一个全新的对象实例，如果需要创建多个结构相同的对象，这种方式不如使用构造函数或类那样高效。</li></ul><h4 id="%E6%AF%94%E8%BE%83-new-object()-%E5%92%8C-%7B%7D" tabindex="-1">比较 <code>new Object()</code> 和 <code>{}</code></h4><p>实际上，<code>new Object()</code> 和对象字面量 <code>{}</code> 在功能上是等价的。对象字面量的语法更简洁、更直观，因此在实际开发中更常被使用。例如，以下两种创建空对象的方式是等效的：</p><pre><code class="language-javascript">let obj1 = new Object();let obj2 = {};</code></pre><p>通常，推荐使用对象字面量的方式，因为它更简洁且易于阅读和维护。<br />然而，了解 <code>new Object()</code> 的存在和用法也是有益的，特别是在需要通过某些特定的构造函数动态决定对象类型的高级用法中。</p><h3 id="3.-%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0" tabindex="-1">3. 构造函数</h3><p>可以定义一个构造函数，然后用 <code>new</code> 关键字来创建对象的实例。</p><pre><code class="language-javascript">function Person(name, age) {  this.name = name;  this.age = age;  this.greet = function() {    console.log(&quot;Hello, &quot; + this.name);  };}let person1 = new Person(&quot;Bob&quot;, 30);</code></pre><p><strong>优点</strong>：适合创建多个具有相似属性和方法的对象。<br /><strong>缺点</strong>：每个实例都会重新定义方法，可能导致内存浪费。</p><h3 id="4.-object.create()" tabindex="-1">4. Object.create()</h3><p><code>Object.create()</code> 方法可以用来创建一个新对象，使用现有的对象来提供新创建的对象的<code>__proto__</code>。</p><pre><code class="language-javascript">const proto = {  greet: function() {    console.log(&quot;Hello, &quot; + this.name);  }};let person = Object.create(proto);person.name = &quot;Charlie&quot;;person.age = 20;person.greet();</code></pre><p><strong>优点</strong>：可以指定原型对象，适合实现原型继承。<br /><strong>缺点</strong>：不如构造函数直观，使用稍复杂。</p><h3 id="5.-%E7%B1%BB%EF%BC%88es6%2B%EF%BC%89" tabindex="-1">5. 类（ES6+）</h3><p>ES6 引入了类的概念，使得创建对象更接近传统面向对象编程。</p><pre><code class="language-javascript">class Person {  constructor(name, age) {    this.name = name;    this.age = age;  }  greet() {    console.log(&quot;Hello, &quot; + this.name);  }}let person = new Person(&quot;Dave&quot;, 40);</code></pre><p><strong>优点</strong>：语法清晰，易于理解和继承，更接近传统的OOP语法。<br /><strong>缺点</strong>：较新的语法，旧版浏览器可能不支持。</p><h2 id="%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%8B%B7%E8%B4%9D" tabindex="-1">对象的拷贝</h2><p>在 JavaScript 中，理解深拷贝和浅拷贝的区别及其实现方式对于管理复杂数据结构非常重要。以下是深拷贝和浅拷贝的区别和实现方法的详解：</p><h3 id="%E6%B7%B1%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B5%85%E6%8B%B7%E8%B4%9D%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">深拷贝与浅拷贝的区别</h3><ol><li><strong>浅拷贝（Shallow Copy）</strong>：<ul><li>浅拷贝只复制对象的第一层属性。如果属性值是基本数据类型，拷贝的是值；如果属性值是引用数据类型（如对象或数组），拷贝的是内存地址（引用），因此原始数据和拷贝数据会共享相同的引用对象。</li><li>修改引用数据类型的属性时，原始对象和拷贝对象都会受到影响。</li></ul></li><li><strong>深拷贝（Deep Copy）</strong>：<ul><li>深拷贝会递归复制所有层级的属性，确保原始数据和拷贝数据在内存中完全独立。修改拷贝对象不会影响原始对象。</li><li>实现深拷贝通常需要递归调用或使用特定的库函数。</li></ul></li></ol><h3 id="%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E6%B7%B1%E6%8B%B7%E8%B4%9D" tabindex="-1">如何实现深拷贝</h3><ol><li><strong>使用 JSON 方法</strong>：<ul><li>最简单的深拷贝实现可以通过 JSON 的序列化和反序列化完成。但这种方法不能复制函数、<code>undefined</code>、<code>Symbol</code> 等特殊类型的数据，也无法处理循环引用的情况。</li></ul><pre><code class="language-javascript">const obj = {    a: 1,    b: {        c: 2    }};const deepCopy = JSON.parse(JSON.stringify(obj));deepCopy.b.c = 3;console.log(obj.b.c); // 输出 2</code></pre></li><li><strong>使用库（如 Lodash 的 <code>cloneDeep</code> 方法）</strong>：<ul><li>一些 JavaScript 库如 Lodash 提供了深拷贝的实现。这些实现通常更为完整，能处理各种数据类型和复杂的数据结构。</li></ul><pre><code class="language-javascript">import _ from &#39;lodash&#39;;const obj = {    a: 1,    b: {        c: 2    }};const deepCopy = _.cloneDeep(obj);deepCopy.b.c = 3;console.log(obj.b.c); // 输出 2</code></pre></li><li><strong>手动实现递归深拷贝</strong>：<ul><li>你可以手动编写一个递归函数来实现深拷贝。这种方法需要处理各种数据类型和循环引用的问题。</li></ul><pre><code class="language-javascript">function deepClone(obj, hash = new WeakMap()) {    if (obj === null) return null;     if (typeof obj !== &#39;object&#39;) return obj;    if (obj instanceof Date) return new Date(obj);    if (obj instanceof RegExp) return new RegExp(obj);    if (hash.has(obj)) return hash.get(obj);        const cloneObj = new obj.constructor();    hash.set(obj, cloneObj);        for (const key in obj) {        if (obj.hasOwnProperty(key)) {            cloneObj[key] = deepClone(obj[key], hash);        }    }    return cloneObj;}</code></pre></li></ol><h3 id="%E5%BC%80%E5%8F%91%E4%B8%AD%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E6%B5%85%E6%8B%B7%E8%B4%9D" tabindex="-1">开发中如何避免浅拷贝</h3><ul><li>在处理复杂的数据结构时，尤其是包含嵌套对象或数组时，避免使用如 <code>Object.assign()</code> 或展开运算符（<code>...</code>）这样的浅拷贝方法。</li><li>使用深拷贝方法（如上述的 JSON 方法或 Lodash 的 <code>cloneDeep</code>）来确保数据的完整独立性。</li><li>在不需要完整拷贝对象的情况下，明确你的需求，选择合适的拷贝策略。</li></ul><h3 id="%E5%B1%95%E5%BC%80%E8%BF%90%E7%AE%97%E7%AC%A6%E6%98%AF%E6%B7%B1%E6%8B%B7%E8%B4%9D%E8%BF%98%E6%98%AF%E6%B5%85%E6%8B%B7%E8%B4%9D" tabindex="-1">展开运算符是深拷贝还是浅拷贝</h3><p>展开运算符（spread operator）在 JavaScript 中用于“展开”数组或对象的元素。<br />当用于对象或数组时，展开运算符仅复制第一层的元素到新的数组或对象中。<br />这意味着如果原始数据结构中包含嵌套的对象或数组，这些嵌套的结构不会被真正地复制，而是复制它们的引用。<br />因此，展开运算符实际上进行的是浅拷贝。</p><h4 id="%E7%A4%BA%E4%BE%8B%EF%BC%9A%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%B5%85%E6%8B%B7%E8%B4%9D" tabindex="-1">示例：对象的浅拷贝</h4><pre><code class="language-javascript">const original = {    a: 1,    b: {        c: 2    }};const copied = { ...original };copied.a = 3; // 修改基本类型值copied.b.c = 3; // 修改引用类型值console.log(original); // 输出：{ a: 1, b: { c: 3 } }console.log(copied);  // 输出：{ a: 3, b: { c: 3 } }</code></pre><p>在这个例子中，修改 <code>copied.b.c</code> 同时也改变了 <code>original.b.c</code>，因为 <code>b</code> 属性的值（一个对象）通过引用被复制到了 <code>copied</code> 对象中。</p><h4 id="%E7%A4%BA%E4%BE%8B%EF%BC%9A%E6%95%B0%E7%BB%84%E7%9A%84%E6%B5%85%E6%8B%B7%E8%B4%9D" tabindex="-1">示例：数组的浅拷贝</h4><pre><code class="language-javascript">const originalArray = [1, { b: 2 }, 3];const copiedArray = [...originalArray];copiedArray[1].b = 3; // 修改数组中对象的属性console.log(originalArray); // 输出：[1, { b: 3 }, 3]console.log(copiedArray);  // 输出：[1, { b: 3 }, 3]</code></pre><p>这个例子同样展示了修改 <code>copiedArray</code> 中的对象属性 <code>b</code> 时，<code>originalArray</code> 中相应的属性也被改变了。这是因为数组中的对象是通过引用被复制的。</p><h2 id="%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%9Ajs-%E4%B8%AD%E6%9C%89%E5%93%AA%E4%BA%9B%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%EF%BC%9F" tabindex="-1">补充知识点：JS 中有哪些数据类型？</h2><p>在 JavaScript 中，数据类型分为两大类：基本数据类型（Primitive types）和引用数据类型（Reference types）。<br />理解这两种类型的区别对于掌握 JavaScript 的数据操作和性能优化非常重要。</p><h3 id="%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%EF%BC%88primitive-types%EF%BC%89" tabindex="-1">基本数据类型（Primitive types）</h3><p>基本数据类型直接存储在栈（Stack）中，它们的值是不可变的。<br />当你对基本数据类型的变量进行操作时，实际上是在操作它的值的副本，而不是原始值本身。JavaScript 中的基本数据类型包括：</p><ol><li><strong>Number</strong>: 用于表示整数或浮点数，也包括特殊的数值如 <code>Infinity</code>, <code>-Infinity</code>, 和 <code>NaN</code>。</li><li><strong>BigInt</strong>: 用于表示非常大的整数，超出了 <code>Number</code> 类型能够表示的范围。</li><li><strong>String</strong>: 由字符组成的序列，用于表示文本数据，使用单引号、双引号或反引号表示。</li><li><strong>Boolean</strong>: 表示逻辑实体，只有两个值，<code>true</code> 和 <code>false</code>，用于逻辑判断。</li><li><strong>Undefined</strong>: 一个变量被声明了，但没有赋值时，它的值就是 <code>undefined</code>。</li><li><strong>Null</strong>: 表示没有值或空值。通常用来表示变量将不会存储任何值。</li><li><strong>Symbol</strong>: ES6 引入的新的基本数据类型，每个 <code>Symbol</code> 的值都是唯一的，常用于创建对象的私有成员。</li></ol><h3 id="%E5%BC%95%E7%94%A8%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%EF%BC%88reference-types%EF%BC%89" tabindex="-1">引用数据类型（Reference types）</h3><p>引用数据类型的值是对象，存储在堆（Heap）中，变量实际上存储的是一个指向堆内存中实际对象的指针。这意味着当你操作一个对象时，你是在操作对象的引用而不是实际的对象。引用数据类型包括：</p><ol><li><strong>Object</strong>: 基本的对象类型，几乎所有的 JavaScript 对象都是 <code>Object</code> 类型的实例。<ul><li><strong>Date</strong>: 用于处理日期和时间。</li><li><strong>RegExp</strong>: 用于定义正则表达式。</li><li><strong>Array</strong>和<strong>Function</strong>也都是对象。</li><li>其他如 <code>Map</code>, <code>Set</code>, <code>WeakMap</code>, <code>WeakSet</code> 等也属于对象。</li></ul></li><li><strong>Array</strong>: 用于存储有序集合的对象。</li><li><strong>Function</strong>: 函数实际上是一种特殊类型的对象，它具有可被执行的功能。</li></ol><h3 id="%E7%89%B9%E6%AE%8A%E6%8F%90%E5%8F%8A" tabindex="-1">特殊提及</h3><blockquote><p><strong>BigInt</strong>: ES2020 引入的一种新的数字类型，可以表示非常大的整数。<br />传统的 <code>Number</code> 类型只能安全地表示 <code>-2^53 + 1</code> 到 <code>2^53 - 1</code> 之间的整数，而 <code>BigInt</code> 可以表示任意大的整数。</p></blockquote><h3 id="%E7%B1%BB%E5%9E%8B%E6%A3%80%E6%B5%8B" tabindex="-1">类型检测</h3><p>可以使用 <code>typeof</code> 操作符来检查一个变量的类型，除了 <code>null</code> 返回的是 <code>&quot;object&quot;</code>。<br />对于更复杂的类型判断，通常使用 <code>instanceof</code> 操作符或者 <code>Object.prototype.toString.call()</code> 方法。</p><h3 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h3><ul><li><strong>基本数据类型</strong>：Number, String, Boolean, Undefined, Null, Symbol, BigInt</li><li><strong>引用数据类型</strong>：Object (包括 Array, Function, Date, RegExp 等)<br />JavaScript 的灵活性在很大程度上来自于它的动态类型系统和对各种数据类型的支持。<br />理解这些数据类型及其使用场景对于编写有效和高效的 JavaScript 代码至关重要。<br />了解这些类型及其分类有助于更好地理解 JavaScript 的内存管理和性能优化，以及如何在代码中有效地处理数据。</li></ul><h2 id="%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E5%90%88%E9%9B%86" tabindex="-1">面试问题合集</h2><p>恭喜你耐心看完本文了，对照下方的问题列表，自我提问一下吧~</p><blockquote><p>js中对象的作用是什么？<br />js中如何创建对象？有哪些方式？<br />js中创建对象不同方式的区别是什么？优缺点有哪些？<br />js中对象的深拷贝和浅拷贝有什么区别？<br />js中如何实现对象的深拷贝？<br />js中开发中如何避免发生对象的浅拷贝？<br />js中的展开运算符是深拷贝还是浅拷贝？<br />js中有哪些数据类型？</p></blockquote><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJs/blob/master/js-blog/2024/2024-07/js-object-create-clone.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[记录工作中常用的 JS 数组相关操作  ]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/js-array-work" />
                <id>tag:https://fx67ll.xyz,2024-06-06:js-array-work</id>
                <published>2024-06-06T15:21:42+08:00</published>
                <updated>2025-07-02T11:25:55+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>工作中难免会遇到各种各样的数据结构，较为全面的了解数组操作，对于复杂数据结构的处理会非常有用且节省时间</p><p>所以想在这里总结一下工作中常用的数组操作，都是一些非常基础的知识，大家看个乐就好~</p><h2 id="%E7%9B%AE%E5%BD%95" tabindex="-1">目录</h2><ul><li><a href="#%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95">工作中常用的数组方法</a><ul><li><a href="#%E5%B8%B8%E8%A7%81%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95%E4%B8%AD%E7%9A%84%E7%94%A8%E6%B3%95%E3%80%81%E4%BB%A5%E5%8F%8A%E5%9D%91">常见数组方法中的用法、以及坑</a><ul><li><a href="#slice-%E5%92%8C-splice-%E6%96%B9%E6%B3%95%E4%B9%8B%E9%97%B4%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB">slice() 和 splice() 方法之间有什么区别</a></li><li><a href="#%E5%A6%82%E4%BD%95%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84">如何合并两个数组</a></li><li><a href="#slice%E3%80%81concat%E3%80%81arr-%E6%96%B9%E6%B3%95%E8%BF%94%E5%9B%9E%E7%9A%84%E6%95%B0%E7%BB%84%E6%98%AF%E6%B5%85%E6%8B%B7%E8%B4%9D%E8%BF%98%E6%98%AF%E6%B7%B1%E6%8B%B7%E8%B4%9D">slice()、concat()、[…arr] 方法返回的数组是浅拷贝还是深拷贝</a></li><li><a href="#%E5%A6%82%E4%BD%95%E6%9C%89%E6%95%88%E5%9C%B0%E6%90%9C%E7%B4%A2%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0">如何有效地搜索数组中的元素</a></li><li><a href="#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8reduce%E6%96%B9%E6%B3%95">如何使用reduce()方法</a></li><li><a href="#sort-%E6%96%B9%E6%B3%95%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%AF%B9%E7%BA%AF%E6%95%B0%E5%AD%97%E6%95%B0%E7%BB%84%E8%BF%9B%E8%A1%8C%E6%AD%A3%E7%A1%AE%E7%9A%84%E6%8E%92%E5%BA%8F">sort() 方法是否可以对纯数字数组进行正确的排序</a></li></ul></li></ul></li><li><a href="#sort-%E6%96%B9%E6%B3%95%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%92%E5%BA%8F">sort() 方法如何进行自定义排序</a><ul><li><a href="#%E6%95%B0%E5%AD%97%E6%8E%92%E5%BA%8F">数字排序</a></li><li><a href="#%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8E%92%E5%BA%8F%E8%80%83%E8%99%91%E5%A4%A7%E5%B0%8F%E5%86%99">字符串排序（考虑大小写）</a></li><li><a href="#%E5%A4%8D%E6%9D%82%E5%AF%B9%E8%B1%A1%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F">复杂对象数组排序</a></li></ul></li><li><a href="#%E5%A6%82%E4%BD%95%E5%8E%BB%E9%99%A4%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0">如何去除数组中的重复元素</a><ul><li><a href="#%E4%BD%BF%E7%94%A8-set-%E5%AF%B9%E8%B1%A1">使用 Set 对象</a></li><li><a href="#%E4%BD%BF%E7%94%A8-filter-%E6%96%B9%E6%B3%95">使用 filter() 方法</a></li><li><a href="#%E4%BD%BF%E7%94%A8-reduce-%E6%96%B9%E6%B3%95">使用 reduce() 方法</a></li><li><a href="#%E4%BD%BF%E7%94%A8%E5%AF%B9%E8%B1%A1%E9%94%AE">使用对象键</a></li></ul></li><li><a href="#%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E5%8F%98%E9%87%8F%E6%98%AF%E5%90%A6%E6%98%AF%E6%95%B0%E7%BB%84%E6%9C%89%E5%93%AA%E4%BA%9B%E5%88%A4%E6%96%AD%E6%96%B9%E5%BC%8F">如何判断一个变量是否是数组？有哪些判断方式？</a><ul><li><a href="#%E4%BD%BF%E7%94%A8-arrayisarray-%E6%96%B9%E6%B3%95%E6%8E%A8%E8%8D%90">使用 Array.isArray() 方法（推荐）</a></li><li><a href="#%E4%BD%BF%E7%94%A8-instanceof-%E6%93%8D%E4%BD%9C%E7%AC%A6">使用 instanceof 操作符</a></li><li><a href="#%E4%BD%BF%E7%94%A8-objectprototypetostringcall">使用 Object.prototype.toString.call()</a></li><li><a href="#%E4%BD%BF%E7%94%A8%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E5%B1%9E%E6%80%A7-constructor">使用构造函数属性 constructor</a></li><li><a href="#%E4%BD%BF%E7%94%A8-arrayprototypeisprototypeof">使用 Array.prototype.isPrototypeOf()</a></li></ul></li><li><a href="#if-%E6%9D%A1%E4%BB%B6%E4%B8%AD%E7%9A%84%E7%A9%BA%E6%95%B0%E7%BB%84%E6%98%AF-false-%E8%BF%98%E6%98%AF-true">IF 条件中的空数组是 false 还是 true</a><ul><li><a href="#js-%E4%B8%AD%E7%9A%84%E5%93%AA%E4%BA%9B%E5%80%BC%E4%BC%9A%E8%A2%AB%E5%88%A4%E6%96%AD%E4%B8%BA-falsy-%E5%80%BC">JS 中的哪些值会被判断为 Falsy 值</a></li><li><a href="#%E5%88%A4%E6%96%AD%E4%B8%BA-falsy-%E5%80%BC%E7%9A%84%E7%9B%B8%E5%85%B3%E7%A4%BA%E4%BE%8B">判断为 Falsy 值的相关示例</a></li></ul></li><li><a href="#%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E5%90%88%E9%9B%86">面试问题合集</a></li></ul><h2 id="%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95" tabindex="-1">工作中常用的数组方法</h2><ol><li><strong><code>push()</code></strong> - 向数组的末尾添加一个或多个元素，并返回新的长度。</li><li><strong><code>pop()</code></strong> - 删除数组的最后一个元素并返回该元素。</li><li><strong><code>shift()</code></strong> - 删除数组的第一个元素并返回该元素，数组中的其他元素向前移动。</li><li><strong><code>unshift()</code></strong> - 向数组的开头添加一个或多个元素，并返回新的长度。</li><li><strong><code>slice()</code></strong> - 返回数组的一个片段或子数组。</li><li><strong><code>splice()</code></strong> - 用于插入、删除或替换数组的元素。</li><li><strong><code>map()</code></strong> - 创建一个新数组，其结果是该数组中的每个元素都调用一个提供的函数后的返回值。</li><li><strong><code>filter()</code></strong> - 创建一个新数组，包含通过所提供函数实现的测试的所有元素。</li><li><strong><code>reduce()</code></strong> - 对累加器和数组中的每个元素（从左到右）应用一个函数，将其减少为单个值。</li><li><strong><code>forEach()</code></strong> - 对数组的每个元素执行一次提供的函数。</li><li><strong><code>find()</code></strong> - 返回数组中满足提供的测试函数的第一个元素的值，否则返回 <code>undefined</code>。</li><li><strong><code>findIndex()</code></strong> - 返回数组中满足提供的测试函数的第一个元素的索引，否则返回 <code>-1</code>。</li><li><strong><code>sort()</code></strong> - 对数组的元素进行排序，并返回数组。</li><li><strong><code>reverse()</code></strong> - 颠倒数组中元素的顺序。</li><li><strong><code>concat()</code></strong> - 用于合并两个或多个数组。</li><li><strong><code>join()</code></strong> - 把数组的所有元素放入一个字符串。</li><li><strong><code>includes()</code></strong> - 判断一个数组是否包含一个指定的值，根据情况返回 true 或 false。</li><li><strong><code>some()</code></strong> - 测试数组中是不是至少有一个元素通过了被提供的函数测试。</li><li><strong><code>every()</code></strong> - 测试一个数组内的所有元素是否都能通过某个指定函数的测试。</li></ol><h3 id="%E5%B8%B8%E8%A7%81%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95%E4%B8%AD%E7%9A%84%E7%94%A8%E6%B3%95%E3%80%81%E4%BB%A5%E5%8F%8A%E5%9D%91" tabindex="-1">常见数组方法中的用法、以及坑</h3><h4 id="slice()-%E5%92%8C-splice()-%E6%96%B9%E6%B3%95%E4%B9%8B%E9%97%B4%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB" tabindex="-1"><code>slice()</code> 和 <code>splice()</code> 方法之间有什么区别</h4><ul><li><code>slice()</code>方法返回数组的一个副本，从开始到结束（不包括结束），不改变原数组。</li><li><code>splice()</code>方法通过删除或替换现有元素或添加新元素来修改数组，并返回被修改的元素数组。在需要同时对数组进行元素的删除和添加操作时非常有用。</li></ul><h4 id="%E5%A6%82%E4%BD%95%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84" tabindex="-1">如何合并两个数组</h4><ul><li>可以使用<code>concat()</code>方法或者展开运算符（<code>...</code>）。例如，<code>arr1.concat(arr2)</code>或<code>[...arr1, ...arr2]</code>。</li><li><strong><code>concat()</code></strong>：用于合并多个数组成一个新数组，常用于不改变原数组的情况下创建新的数组集合。</li></ul><h4 id="slice()%E3%80%81concat()%E3%80%81%5B...arr%5D-%E6%96%B9%E6%B3%95%E8%BF%94%E5%9B%9E%E7%9A%84%E6%95%B0%E7%BB%84%E6%98%AF%E6%B5%85%E6%8B%B7%E8%B4%9D%E8%BF%98%E6%98%AF%E6%B7%B1%E6%8B%B7%E8%B4%9D" tabindex="-1"><code>slice()</code>、<code>concat()</code>、<code>[...arr]</code> 方法返回的数组是浅拷贝还是深拷贝</h4><p>使用<code>slice()</code>、<code>concat()</code>、<code>[...arr]</code>是浅拷贝操作。浅拷贝是指只复制对象的第一层属性，如果属性是引用类型，复制的是引用。</p><h4 id="%E5%A6%82%E4%BD%95%E6%9C%89%E6%95%88%E5%9C%B0%E6%90%9C%E7%B4%A2%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%85%83%E7%B4%A0" tabindex="-1">如何有效地搜索数组中的元素</h4><ul><li>使用<code>indexOf()</code>或<code>includes()</code>来检查元素是否存在。</li><li>使用<code>find()</code>或<code>findIndex()</code>来查找符合条件的元素或其索引。</li><li>这些方法都会在找到符合条件的第一个元素后停止搜索。</li></ul><h4 id="%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8reduce()%E6%96%B9%E6%B3%95" tabindex="-1">如何使用<code>reduce()</code>方法</h4><ul><li><code>reduce()</code>方法对数组中的每个元素执行一个由您提供的reducer函数（升序执行），将其结果汇总为单个返回值。</li><li>例如，计算数组所有值的总和：<code>array.reduce((acc, val) =&gt; acc + val, 0)</code>。</li></ul><h4 id="sort()-%E6%96%B9%E6%B3%95%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%AF%B9%E7%BA%AF%E6%95%B0%E5%AD%97%E6%95%B0%E7%BB%84%E8%BF%9B%E8%A1%8C%E6%AD%A3%E7%A1%AE%E7%9A%84%E6%8E%92%E5%BA%8F" tabindex="-1"><code>sort()</code> 方法是否可以对纯数字数组进行正确的排序</h4><ul><li><code>sort()</code> 方法可以用来按数字大小排序数组中的数字。</li><li>在JavaScript中，如果直接使用 <code>sort()</code> 方法而不提供比较函数，它默认会将数组元素转换成字符串，然后按照字符串的<code>Unicode</code>字典顺序进行排序。</li><li>这种默认行为对于数字排序通常是不正确的，特别是当数字的位数不一致时。</li></ul><pre><code class="language-javascript">let numbers = [10, 5, 100, 2, 1000];numbers.sort();console.log(numbers); // 输出: [10, 100, 1000, 2, 5]</code></pre><h2 id="sort()-%E6%96%B9%E6%B3%95%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%92%E5%BA%8F" tabindex="-1"><code>sort()</code> 方法如何进行自定义排序</h2><p>在JavaScript中，<code>Array.prototype.sort()</code> 方法可以接受一个可选的比较函数作为参数，用来定义数组元素的排序方式。<br />这个比较函数应该接受两个参数（通常表示为<code>a</code>和<code>b</code>），这两个参数是数组中将要被比较的两个元素。比较函数的返回值决定了这两个元素在最终排序后数组中的顺序：</p><ul><li>如果比较函数返回一个小于0的值，那么元素<code>a</code>将排在元素<code>b</code>之前。</li><li>如果比较函数返回一个大于0的值，那么元素<code>b</code>将排在元素<code>a</code>之前。</li><li>如果比较函数返回0，那么元素<code>a</code>和<code>b</code>的顺序不变。</li></ul><h3 id="sort()%E6%96%B9%E6%B3%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8E%92%E5%BA%8F%E7%9A%84%E5%9F%BA%E7%A1%80%E7%A4%BA%E4%BE%8B" tabindex="-1"><code>sort()</code>方法自定义排序的基础示例</h3><h4 id="1.-%E6%95%B0%E5%AD%97%E6%8E%92%E5%BA%8F" tabindex="-1">1. 数字排序</h4><p>对于数字类型的数组，可以这样定义比较函数来确保数组按照数值大小升序或降序排列：</p><pre><code class="language-javascript">let numbers = [10, 5, 100, 2, 1000];// 升序排序numbers.sort((a, b) =&gt; a - b);console.log(numbers); // 输出: [2, 5, 10, 100, 1000]// 降序排序numbers.sort((a, b) =&gt; b - a);console.log(numbers); // 输出: [1000, 100, 10, 5, 2]</code></pre><h4 id="2.-%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8E%92%E5%BA%8F%EF%BC%88%E8%80%83%E8%99%91%E5%A4%A7%E5%B0%8F%E5%86%99%EF%BC%89" tabindex="-1">2. 字符串排序（考虑大小写）</h4><p>对于字符串数组，如果要按字典顺序排序，可能还需要考虑大小写：</p><pre><code class="language-javascript">let words = [&#39;banana&#39;, &#39;Apple&#39;, &#39;orange&#39;];// 升序排序，忽略大小写words.sort((a, b) =&gt; a.toLowerCase().localeCompare(b.toLowerCase()));console.log(words); // 输出: [&#39;Apple&#39;, &#39;banana&#39;, &#39;orange&#39;]</code></pre><h4 id="3.-%E5%A4%8D%E6%9D%82%E5%AF%B9%E8%B1%A1%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F" tabindex="-1">3. 复杂对象数组排序</h4><p>如果数组包含对象，可以根据对象的某个属性来排序：</p><pre><code class="language-javascript">let items = [    { name: &quot;John&quot;, age: 25 },    { name: &quot;Anna&quot;, age: 30 },    { name: &quot;Mike&quot;, age: 22 }];// 根据age属性升序排序items.sort((a, b) =&gt; a.age - b.age);console.log(items); // 输出: [{ name: &quot;Mike&quot;, age: 22 }, { name: &quot;John&quot;, age: 25 }, { name: &quot;Anna&quot;, age: 30 }]</code></pre><h3 id="sort()%E6%96%B9%E6%B3%95%E5%9C%A8%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%BA%94%E7%94%A8%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1"><code>sort()</code>方法在工作中应用的注意事项</h3><ul><li>使用<code>sort()</code>方法时需要注意，它是在原数组上进行排序，而不是返回一个新的排序后的数组。</li><li>如果不提供比较函数，<code>sort()</code>将元素转换为字符串，并按照字符串的Unicode码点顺序排序，这可能不会按照数值大小排序数字。</li><li>通过提供自定义的比较函数，可以灵活地控制数组的排序行为，满足不同的业务需求。</li></ul><h2 id="%E5%A6%82%E4%BD%95%E5%8E%BB%E9%99%A4%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0" tabindex="-1">如何去除数组中的重复元素</h2><h3 id="1.-%E4%BD%BF%E7%94%A8-set-%E5%AF%B9%E8%B1%A1" tabindex="-1">1. 使用 <code>Set</code> 对象</h3><p><code>Set</code> 是一种允许存储任何类型唯一值的集合。由于 <code>Set</code> 自动去除重复的元素，我们可以利用这个特性来去除数组中的重复项。</p><pre><code class="language-javascript">const array = [1, 2, 2, 3, 4, 4, 5];const uniqueArray = [...new Set(array)];console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]</code></pre><h3 id="2.-%E4%BD%BF%E7%94%A8-filter()-%E6%96%B9%E6%B3%95" tabindex="-1">2. 使用 <code>filter()</code> 方法</h3><p><code>filter()</code> 方法创建一个新数组，其包含通过所提供函数实现的测试的所有元素。可以利用这个方法和 <code>indexOf()</code> 来过滤掉重复的元素。</p><pre><code class="language-javascript">const array = [1, 2, 2, 3, 4, 4, 5];const uniqueArray = array.filter((item, index) =&gt; array.indexOf(item) === index);console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]</code></pre><h3 id="3.-%E4%BD%BF%E7%94%A8-reduce()-%E6%96%B9%E6%B3%95" tabindex="-1">3. 使用 <code>reduce()</code> 方法</h3><p><code>reduce()</code> 方法对数组中的每个元素执行一个 reducer 函数（升序执行），将其结果汇总为单个返回值。这个方法也可以用来去除重复元素。</p><pre><code class="language-javascript">const array = [1, 2, 2, 3, 4, 4, 5];const uniqueArray = array.reduce((acc, current) =&gt; {  if (!acc.includes(current)) {    acc.push(current);  }  return acc;}, []);console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]</code></pre><h3 id="4.-%E4%BD%BF%E7%94%A8%E5%AF%B9%E8%B1%A1%E9%94%AE" tabindex="-1">4. 使用对象键</h3><p>利用对象的属性不能重复的特性，可以快速去除数组中的重复元素。</p><pre><code class="language-javascript">const array = [1, 2, 2, 3, 4, 4, 5];const uniqueObj = {};array.forEach(item =&gt; {  uniqueObj[item] = true;});const uniqueArray = Object.keys(uniqueObj).map(key =&gt; parseInt(key));console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]</code></pre><h2 id="%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E5%8F%98%E9%87%8F%E6%98%AF%E5%90%A6%E6%98%AF%E6%95%B0%E7%BB%84%EF%BC%9F%E6%9C%89%E5%93%AA%E4%BA%9B%E5%88%A4%E6%96%AD%E6%96%B9%E5%BC%8F%EF%BC%9F" tabindex="-1">如何判断一个变量是否是数组？有哪些判断方式？</h2><p>在JavaScript中，判断一个变量是否是数组可以通过多种方式实现。这些方法各有优缺点，适用于不同的场景。</p><ul><li><strong>最推荐的方法</strong>：<code>Array.isArray()</code>，因为它简单且总是可靠的。</li><li><strong>较可靠的方法</strong>：<code>Object.prototype.toString.call()</code>，尤其是在涉及多个执行环境时。</li><li><strong>其他方法</strong>：虽然 <code>instanceof</code>、<code>constructor</code> 和 <code>isPrototypeOf</code> 可以用来判断数组，但它们在某些情况下可能不够可靠或容易受到环境的影响。</li></ul><h3 id="1.-%E4%BD%BF%E7%94%A8-array.isarray()-%E6%96%B9%E6%B3%95%EF%BC%88%E6%8E%A8%E8%8D%90%EF%BC%89" tabindex="-1">1. 使用 <code>Array.isArray()</code> 方法（推荐）</h3><p><code>Array.isArray()</code> 是 ECMAScript 5 新增的方法，它提供了一种简单、可靠的方式来判断一个变量是否是数组。这是判断数组的最推荐方式。</p><pre><code class="language-javascript">let arr = [1, 2, 3];console.log(Array.isArray(arr)); // 输出：true</code></pre><h3 id="2.-%E4%BD%BF%E7%94%A8-instanceof-%E6%93%8D%E4%BD%9C%E7%AC%A6" tabindex="-1">2. 使用 <code>instanceof</code> 操作符</h3><p><code>instanceof</code> 操作符用于测试一个对象在其原型链中是否存在一个构造函数的 <code>prototype</code> 属性。</p><pre><code class="language-javascript">let arr = [1, 2, 3];console.log(arr instanceof Array); // 输出：true</code></pre><p>但是，<code>instanceof</code> 可能在不同的执行环境（如不同的iframe或window对象）中不一定可靠，因为数组的构造器可能不同。</p><h3 id="3.-%E4%BD%BF%E7%94%A8-object.prototype.tostring.call()" tabindex="-1">3. 使用 <code>Object.prototype.toString.call()</code></h3><p>这是一个更加通用的方法，可以用来获取任意对象的类型。对于数组，<code>Object.prototype.toString.call()</code> 返回 <code>&quot;[object Array]&quot;</code>。</p><pre><code class="language-javascript">let arr = [1, 2, 3];console.log(Object.prototype.toString.call(arr) === &#39;[object Array]&#39;); // 输出：true</code></pre><p>这种方法相对可靠，并且在不同的执行环境中也能保持一致性。</p><h3 id="4.-%E4%BD%BF%E7%94%A8%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E5%B1%9E%E6%80%A7-constructor" tabindex="-1">4. 使用构造函数属性 <code>constructor</code></h3><p>每个数组都有一个 <code>constructor</code> 属性，指向它的构造函数。如果这个属性是 <code>Array</code>，那么对象通常是数组。但这种方法不是非常可靠，因为 <code>constructor</code> 属性可以被改写。</p><pre><code class="language-javascript">let arr = [1, 2, 3];console.log(arr.constructor === Array); // 输出：true</code></pre><h3 id="5.-%E4%BD%BF%E7%94%A8-array.prototype.isprototypeof()" tabindex="-1">5. 使用 <code>Array.prototype.isPrototypeOf()</code></h3><p>这个方法可以用来检查 <code>Array.prototype</code> 是否存在于某个对象的原型链上。</p><pre><code class="language-javascript">let arr = [1, 2, 3];console.log(Array.prototype.isPrototypeOf(arr)); // 输出：true</code></pre><h2 id="if-%E6%9D%A1%E4%BB%B6%E4%B8%AD%E7%9A%84%E7%A9%BA%E6%95%B0%E7%BB%84%E6%98%AF-false-%E8%BF%98%E6%98%AF-true" tabindex="-1">IF 条件中的空数组是 <code>false</code> 还是 <code>true</code></h2><p>在 JavaScript 中，空数组（<code>[]</code>）在 <code>if</code> 条件判断中被视为 <code>true</code>。<br />这是因为所有对象，包括数组（无论是否为空），在布尔上下文中都被视为 <code>true</code>。<br />这意味着即使数组没有任何元素，它仍然被认为是真值。</p><h3 id="js-%E4%B8%AD%E7%9A%84%E5%93%AA%E4%BA%9B%E5%80%BC%E4%BC%9A%E8%A2%AB%E5%88%A4%E6%96%AD%E4%B8%BA-falsy-%E5%80%BC" tabindex="-1">JS 中的哪些值会被判断为 <code>Falsy</code> 值</h3><p>以下是JavaScript中的所有 <code>Falsy</code> 值：</p><ol><li><strong><code>false</code></strong> - 布尔值 <code>false</code> 本身自然是 <code>Falsy</code>。</li><li><strong><code>0</code></strong> 和 <strong><code>-0</code></strong> - 数字零和负零。</li><li><strong><code>0n</code></strong> - BigInt类型的零。</li><li><strong><code>&quot;&quot;</code></strong>（空字符串） - 双引号或单引号中没有任何字符。</li><li><strong><code>null</code></strong> - 表示无值。</li><li><strong><code>undefined</code></strong> - 变量已声明但未赋值时的默认值。</li><li><strong><code>NaN</code></strong> - 表示 “不是一个数字” 的特殊值。</li></ol><h3 id="%E5%88%A4%E6%96%AD%E4%B8%BA-falsy-%E5%80%BC%E7%9A%84%E7%9B%B8%E5%85%B3%E7%A4%BA%E4%BE%8B" tabindex="-1">判断为 <code>Falsy</code> 值的相关示例</h3><p>在实际的编程实践中，了解哪些值是 <code>Falsy</code> 值非常重要，因为它们会影响条件语句的行为。</p><pre><code class="language-javascript">if (false) {} // 不执行if (0) {} // 不执行if (-0) {} // 不执行if (0n) {} // 不执行if (&quot;&quot;) {} // 不执行if (null) {} // 不执行if (undefined) {} // 不执行if (NaN) {} // 不执行if ([]) {} // 执行，因为空数组是 true</code></pre><h2 id="%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E5%90%88%E9%9B%86" tabindex="-1">面试问题合集</h2><p>恭喜你耐心看完本文了，对照下方的问题列表，自我提问一下吧~</p><blockquote><p>你知道哪些 JS 数组操作？<br />工作中常用哪些方法？<br />slice() 和 splice() 方法之间有什么区别？<br />如何合并两个数组？<br />slice()、concat()、[…arr] 方法返回的数组是浅拷贝还是深拷贝？<br />如何有效地搜索数组中的元素？<br />如何使用reduce()方法？<br />sort() 方法是否可以对纯数字数组进行正确的排序？<br />sort() 方法如何进行自定义排序？<br />如何去除数组中的重复元素？<br />如何判断一个变量是否是数组？<br />IF 条件中的空数组是 false 还是 true？<br />JS 中的哪些值会被判断为 Falsy 值？</p></blockquote><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJs/blob/master/js-blog/2024/2024-06/js-array-work.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数，以及其在工作中的应用和注意事项]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/js-event-loop-job" />
                <id>tag:https://fx67ll.xyz,2024-05-31:js-event-loop-job</id>
                <published>2024-05-31T16:58:35+08:00</published>
                <updated>2025-07-02T11:26:00+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>为什么会突然想到写这么一个大杂烩的博文呢，必须要从笔者几年前的一次面试说起</p><p>当时的我年轻气盛，在简历上放了自己的博客地址，而面试官应该是翻了我的博客，好几道面试题都是围绕着我的博文来提问</p><p>其中一个问题，直接使得空气静止了五分钟，然后面试官结束了这次面试，那就是：如何手写一个简易的Promise对象?</p><p>在这里，我也先挖个坑，给你们五分钟思考并自己回答一下这个问题~ <em>（答案隐藏在文章中自行查看~）</em></p><p>也是自从那次面试，我告诉自己，工作实战中总结的经验，一定要知其然知其所以然，才可以真正用好这些核心知识点，不积跬步，无以至千里</p><p>说了这么多的废话，我们进入今天的博文正题~</p><h2 id="%E7%9B%AE%E5%BD%95" tabindex="-1">目录</h2><ul><li><a href="#%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AFevent-loop">事件循环（Event Loop）</a><ul><li><a href="#%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F">事件循环的执行顺序</a></li></ul></li><li><a href="#%E5%AE%8F%E4%BB%BB%E5%8A%A1macrotasks%E5%92%8C%E5%BE%AE%E4%BB%BB%E5%8A%A1microtasks">宏任务（MacroTasks）和微任务（MicroTasks）</a><ul><li><a href="#%E5%B8%B8%E8%A7%81%E5%AE%8F%E4%BB%BB%E5%8A%A1">常见宏任务</a></li><li><a href="#%E5%B8%B8%E8%A7%81%E5%BE%AE%E4%BB%BB%E5%8A%A1">常见微任务</a></li><li><a href="#%E5%AE%8F%E4%BB%BB%E5%8A%A1%E5%92%8C%E5%BE%AE%E4%BB%BB%E5%8A%A1%E7%9A%84%E5%8C%BA%E5%88%AB">宏任务和微任务的区别</a></li></ul></li><li><a href="#promise-%E5%AF%B9%E8%B1%A1">Promise 对象</a><ul><li><a href="#promise-%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5">Promise 的基本概念</a></li><li><a href="#%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA-promise-%E5%AF%B9%E8%B1%A1">如何创建 Promise 对象</a></li><li><a href="#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-promise-%E5%AF%B9%E8%B1%A1">如何使用 Promise 对象</a></li><li><a href="#promise-%E7%9A%84%E4%BC%98%E5%8A%BF">Promise 的优势</a></li><li><a href="#promise-%E5%9C%A8%E5%B7%A5%E4%BD%9C%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF">Promise 在工作中的应用场景</a></li><li><a href="#%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8%E4%B8%8A%E6%89%8Bjavascript%E4%B8%AD%E7%9A%84-promise">如何快速入门上手 JavaScript 中的 Promise</a></li><li><a href="#%E6%89%8B%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E6%98%93%E7%9A%84-promise-%E5%AF%B9%E8%B1%A1">手写一个简易的 Promise 对象</a></li></ul></li><li><a href="#%E5%AE%9A%E6%97%B6%E5%99%A8%E5%87%BD%E6%95%B0">定时器函数</a><ul><li><a href="#settimeout">setTimeout()</a></li><li><a href="#setinterval">setInterval()</a></li><li><a href="#cleartimeout-%E5%92%8C-clearinterval">clearTimeout() 和 clearInterval()</a></li><li><a href="#%E5%AE%9A%E6%97%B6%E5%99%A8%E5%87%BD%E6%95%B0%E7%9A%84%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F">定时器函数的使用注意</a></li><li><a href="#%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8">销毁定时器</a><ul><li><a href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8">为什么要销毁定时器？</a></li><li><a href="#vue%E4%B8%AD%E5%A6%82%E4%BD%95%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8">Vue 中如何销毁定时器？</a></li><li><a href="#react%E4%B8%AD%E5%A6%82%E4%BD%95%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8">React 中如何销毁定时器？</a></li></ul></li></ul></li><li><a href="#%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86%E7%82%B9">补充知识点</a><ul><li><a href="#requestanimationframe">requestAnimationFrame</a></li><li><a href="#setimmediate">setImmediate</a></li><li><a href="#processnexttick">process.nextTick</a></li><li><a href="#mutationobserver">MutationObserver</a></li></ul></li><li><a href="#%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E5%90%88%E9%9B%86">面试问题合集</a></li></ul><h2 id="%E4%BB%80%E4%B9%88%E6%98%AF%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF%EF%BC%88event-loop%EF%BC%89" tabindex="-1">什么是事件循环（Event Loop）</h2><p>事件循环是JavaScript运行时环境的核心机制，用于协调事件、用户交互、脚本、渲染、网络等。</p><p>由于JavaScript是单线程的，事件循环使得它能够执行非阻塞操作，即使在处理IO等长时间运行的任务时也能保持响应性。</p><h3 id="%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF%E7%9A%84%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F" tabindex="-1">事件循环的执行顺序</h3><p>在JavaScript的执行模型中，事件循环按照以下顺序处理任务：</p><ol><li>执行全局脚本代码，这些同步代码直接运行。</li><li>当执行栈为空时，事件循环会查看微任务队列。如果队列中有微任务，就一直执行微任务直到队列清空。</li><li>执行一个宏任务（如由 <code>setTimeout()</code> 或 <code>setInterval()</code> 设置的回调）。</li><li>宏任务执行完毕后，再次执行所有微任务。</li><li>如果有必要，进行UI渲染。</li><li>开始下一轮事件循环，处理下一个宏任务。</li></ol><p>通过这种机制，JavaScript可以在单线程中有效地处理异步事件，同时保持代码执行的顺序和预期效果。<br />理解这些概念将帮助你更好地设计和调试JavaScript中的异步代码。</p><h2 id="%E4%BB%80%E4%B9%88%E6%98%AF%E5%AE%8F%E4%BB%BB%E5%8A%A1%EF%BC%88macrotasks%EF%BC%89%E5%92%8C-%E5%BE%AE%E4%BB%BB%E5%8A%A1%EF%BC%88microtasks%EF%BC%89" tabindex="-1">什么是宏任务（MacroTasks）和 微任务（MicroTasks）</h2><h3 id="%E5%AE%8F%E4%BB%BB%E5%8A%A1" tabindex="-1">宏任务</h3><p>宏任务是 JavaScript 事件循环中的一个较大的任务单元，每个宏任务在执行时会开启一个新的事件循环</p><p>一个宏任务的完成通常会涉及到一个较为完整的工作流程，例如整个脚本的执行、事件（如用户交互事件）、定时器事件（setTimeout、setInterval）以及浏览器的 UI 渲染等</p><p>每个宏任务在执行完毕后，会从任务队列中清除</p><h4 id="%E5%B8%B8%E8%A7%81%E5%AE%8F%E4%BB%BB%E5%8A%A1" tabindex="-1">常见宏任务</h4><ul><li><code>setTimeout()</code>：用于设置定时器，在指定的时间间隔后执行任务</li><li><code>setInterval()</code>：用于设置定时器，在指定的时间间隔循环执行任务</li><li><code>setImmediate()</code>：类似<code>setTimeout(fn, 0)</code> <em>(仅在Node.js中)</em></li><li>IO操作：例如文件读写、网络请求等</li><li>UI渲染：浏览器需要重新渲染页面时触发的任务</li><li><code>requestAnimationFrame</code>：动画渲染函数</li><li></li></ul><h4 id="%E6%8B%93%E5%B1%95%E6%8F%90%E9%97%AE%EF%BC%9A%E7%82%B9%E5%87%BB%E5%92%8C%E9%94%AE%E7%9B%98%E4%BA%8B%E4%BB%B6%E6%98%AF%E5%AE%8F%E4%BB%BB%E5%8A%A1%E5%90%97%EF%BC%9F" tabindex="-1">拓展提问：点击和键盘事件是宏任务吗？</h4><p>在 JavaScript 中，<strong>事件（如点击和键盘事件）</strong> 通常被处理为任务<br />但它们不是宏任务（macro-tasks）也不是微任务（micro-tasks），而是作为任务队列中的<strong>任务</strong>来处理<br />这些任务在宏任务和微任务之外，有自己的特殊队列，通常称为 <strong>任务队列</strong>（task queue）</p><p><strong>事件（如点击和键盘事件）</strong> 通常被放入任务队列，并且它们被视为任务的一种。当<br />事件循环执行时，它会首先检查宏任务队列，执行完当前宏任务后，再执行所有的微任务。<br />在微任务执行完毕后，浏览器可能会进行渲染操作（如果需要），然后事件循环会继续到下一个宏任务。</p><p>因此，可以说点击和键盘事件是作为任务处理的，而不特定分类为宏任务或微任务。<br />这种机制确保了 JavaScript 可以在单线程环境中高效地处理异步事件和操作，同时保持代码执行的顺序性和可预测性。</p><h3 id="%E5%BE%AE%E4%BB%BB%E5%8A%A1" tabindex="-1">微任务</h3><p>微任务是在当前宏任务执行完毕后立即执行的任务，事件循环会在每个宏任务之后执行所有队列中的微任务<br />它们的执行时机是在下一个宏任务开始之前，当前宏任务的后续阶段，微任务的执行时间早于宏任务<br />微任务通常用于处理异步操作的结果，确保尽可能快地响应</p><h4 id="%E5%B8%B8%E8%A7%81%E5%BE%AE%E4%BB%BB%E5%8A%A1" tabindex="-1">常见微任务</h4><ul><li><code>Promise.then/catch/finally</code></li><li>Promise回调：当Promise状态改变时，会执行相应的回调函数</li><li><code>async</code>/<code>await</code>：使用async函数和await关键字进行异步操作时，await后面的代码会作为微任务执行</li><li><code>process.nextTick</code>：在 Node.js 的事件循环的当前阶段完成后、下一个事件循环阶段开始之前，安排一个回调函数尽快执行 <em>(仅在Node.js中)</em></li><li><code>MutaionObserver()</code>：浏览器中用于观察DOM树的变化，监听DOM变化，当DOM发生变化时触发微任务</li></ul><h3 id="%E5%AE%8F%E4%BB%BB%E5%8A%A1%E5%92%8C%E5%BE%AE%E4%BB%BB%E5%8A%A1%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">宏任务和微任务的区别</h3><h4 id="%E4%BB%BB%E5%8A%A1%E7%89%B9%E5%BE%81" tabindex="-1">任务特征</h4><ol><li><strong>宏任务</strong> 有明确的异步任务需要执行和回调；需要其他异步线程支持</li><li><strong>微任务</strong> 没有明确的异步任务需要执行，只有回调，不需要其他异步线程支持</li></ol><h4 id="%E5%AD%98%E6%94%BE%E4%BD%8D%E7%BD%AE" tabindex="-1">存放位置</h4><ol><li><strong>宏任务</strong> 中的事件放在<code>callback queue</code>中，由事件触发线程维护</li><li><strong>微任务</strong> 的事件放在微任务队列中，由js引擎线程维护</li></ol><h4 id="%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F" tabindex="-1">执行顺序</h4><ol><li>事件循环的过程中，执行栈在同步代码执行完成后，优先检查 <strong>微任务</strong> 队列是否有任务需要执行，如果没有，再去 <strong>宏任务</strong> 队列检查是否有任务执行，如此往复</li><li><strong>微任务</strong> 一般在当前循环就会优先执行，而 <strong>宏任务</strong> 会等到下一次循环</li><li>因此，<strong>微任务</strong> 一般比 <strong>宏任务</strong> 先执行</li></ol><h4 id="%E9%98%9F%E5%88%97%E6%95%B0%E9%87%8F" tabindex="-1">队列数量</h4><ol><li><strong>微任务</strong> 队列只有一个</li><li><strong>宏任务</strong> 队列可能有多个</li></ol><h2 id="%E4%BB%80%E4%B9%88%E6%98%AF-promise-%E5%AF%B9%E8%B1%A1" tabindex="-1">什么是 <code>Promise</code> 对象</h2><p>在 JavaScript 中，<code>Promise</code> 对象是异步编程的一种重要机制，它代表了一个尚未完成但预期将来会完成的操作的最终结果。<br /><code>Promise</code> 提供了一种处理异步操作的方法，使得异步代码易于编写和理解。</p><h3 id="promise-%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5" tabindex="-1"><code>Promise</code> 的基本概念</h3><p><code>Promise</code> 对象有三种状态：</p><ol><li><strong>Pending（等待中）</strong>：初始状态，既不是成功，也不是失败。</li><li><strong>Fulfilled（已完成）</strong>：意味着操作成功完成。</li><li><strong>Rejected（已拒绝）</strong>：意味着操作失败或出现错误。</li></ol><h3 id="%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA-promise-%E5%AF%B9%E8%B1%A1" tabindex="-1">如何创建 <code>Promise</code> 对象</h3><p><code>Promise</code> 对象是通过 <code>new Promise</code> 构造函数创建的，它接收一个执行器函数作为参数。<br />这个执行器函数本身接受两个参数：<code>resolve</code> 和 <code>reject</code>，这两个参数也是函数。<br />当异步操作成功时，调用 <code>resolve</code> 函数；当操作失败时，调用 <code>reject</code> 函数。</p><pre><code class="language-javascript">const myPromise = new Promise((resolve, reject) =&gt; {    // 异步操作    const condition = true;  // 假设这是某种条件判断    if (condition) {        resolve(&#39;Operation successful&#39;);    } else {        reject(&#39;Error occurred&#39;);    }});</code></pre><h3 id="%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-promise-%E5%AF%B9%E8%B1%A1" tabindex="-1">如何使用 <code>Promise</code> 对象</h3><p>一旦 <code>Promise</code> 被解析（resolved）或拒绝（rejected），它就不能更改状态。<br />你可以使用 <code>.then()</code> 方法来处理已完成的 <code>Promise</code>，并使用 <code>.catch()</code> 方法来处理被拒绝的 <code>Promise</code>。<br />还有 <code>.finally()</code> 方法，它在 <code>Promise</code> 完成后被调用，无论其结果如何。</p><pre><code class="language-javascript">myPromise    .then(result =&gt; {        console.log(result);  // 处理结果    })    .catch(error =&gt; {        console.error(error);  // 处理错误    })    .finally(() =&gt; {        console.log(&#39;Operation completed&#39;);  // 最终都会执行    });</code></pre><h3 id="promise-%E7%9A%84%E4%BC%98%E5%8A%BF" tabindex="-1"><code>Promise</code> 的优势</h3><ol><li><strong>链式调用</strong>：<code>Promise</code> 允许你通过 <code>.then()</code> 方法链式调用多个异步操作，每个操作依次执行。</li><li><strong>错误处理</strong>：通过 <code>.catch()</code> 方法，可以集中处理多个异步操作中的错误。</li><li><strong>并行处理</strong>：<code>Promise.all()</code> 方法允许并行执行多个异步操作，并等待所有操作完成。</li></ol><h3 id="promise-%E5%9C%A8%E5%B7%A5%E4%BD%9C%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF" tabindex="-1"><code>Promise</code> 在工作中的应用场景</h3><p><code>Promise</code> 在处理如网络请求、文件操作等异步操作时非常有用，它使得代码更加清晰，减少了回调地狱（callback hell）的问题。<br />通过 <code>Promise</code>，开发者可以写出更加优雅和可维护的异步代码。</p><h3 id="%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8%E4%B8%8A%E6%89%8Bjavascript%E4%B8%AD%E7%9A%84-promise%EF%BC%9F" tabindex="-1">如何快速入门上手JavaScript中的 <code>Promise</code>？</h3><p><a href="https://fx67ll.xyz/archives/promise-quickstart" target="_blank">拓展资料 ———— 快速入门上手JavaScript中的Promise</a></p><h2 id="%E8%A7%A3%E7%AD%94%E6%96%87%E7%AB%A0%E5%BC%80%E5%A4%B4%E7%9A%84%E9%97%AE%E9%A2%98%EF%BC%9A%E5%A6%82%E4%BD%95%E6%89%8B%E5%86%99%E4%B8%80%E4%B8%AA%E7%AE%80%E6%98%93%E7%9A%84-promise-%E5%AF%B9%E8%B1%A1%3F" tabindex="-1">解答文章开头的问题：如何手写一个简易的 <code>Promise</code> 对象?</h2><pre><code class="language-js">function SimplePromise(executor) {  let onResolve, onReject;  let fulfilled = false;  let rejected = false;  let called = false; // 防止resolve和reject被多次调用  let value;  let reason;  // resolve函数  function resolve(val) {    if (!called) {      value = val;      fulfilled = true;      called = true;      if (onResolve) {        onResolve(val);      }    }  }  // reject函数  function reject(err) {    if (!called) {      reason = err;      rejected = true;      called = true;      if (onReject) {        onReject(err);      }    }  }  // then方法  this.then = function(callback) {    onResolve = callback;    if (fulfilled) {      onResolve(value);    }    return this; // 支持链式调用  };  // catch方法  this.catch = function(callback) {    onReject = callback;    if (rejected) {      onReject(reason);    }    return this; // 支持链式调用  };  // 立即执行传入的executor函数  try {    executor(resolve, reject);  } catch (error) {    reject(error);  }}// 使用示例let promise = new SimplePromise((resolve, reject) =&gt; {  setTimeout(() =&gt; {    resolve(&quot;Success!&quot;);    // reject(&quot;Error!&quot;); // 也可以测试reject情况  }, 1000);});promise.then(result =&gt; {  console.log(result); // 输出 &quot;Success!&quot;}).catch(error =&gt; {  console.log(error);});</code></pre><h2 id="%E4%BB%80%E4%B9%88%E6%98%AF%E5%AE%9A%E6%97%B6%E5%99%A8%E5%87%BD%E6%95%B0" tabindex="-1">什么是定时器函数</h2><p>JavaScript 中的定时器函数允许你在一定时间后或者以指定的时间间隔重复执行代码。<br />这些功能主要通过两个全局函数实现：<code>setTimeout()</code> 和 <code>setInterval()</code>。<br />这些函数是异步的，意味着它们不会阻塞代码的执行，而是在指定的延时后将任务加入到 JavaScript 的事件队列中，等待当前执行栈清空后再执行。</p><h3 id="settimeout()" tabindex="-1">setTimeout()</h3><p><code>setTimeout()</code> 函数用于在指定的毫秒数后执行一个函数或指定的代码。<br />它不会阻止后续代码的执行，而是在背后计时，一旦时间到达，就将回调函数加入到事件队列中，等待执行。</p><h4 id="%E8%AF%AD%E6%B3%95" tabindex="-1">语法</h4><pre><code class="language-javascript">let timeoutID = setTimeout(function[, delay, arg1, arg2, ...]);</code></pre><ul><li><code>function</code>：要执行的函数。</li><li><code>delay</code>：延迟的时间，以毫秒为单位。如果省略，或者为 0，浏览器通常会有最小延迟时间（在HTML5标准中定义为4ms）。</li><li><code>arg1, arg2, ...</code>：传递给函数的额外参数。</li></ul><h4 id="%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B" tabindex="-1">使用示例</h4><pre><code class="language-javascript">console.log(&quot;Hello&quot;);setTimeout(() =&gt; {  console.log(&quot;World!&quot;);}, 1000);</code></pre><p><em>这个例子会先打印 “Hello”，然后大约1秒后打印 “World!”</em></p><h3 id="setinterval()" tabindex="-1">setInterval()</h3><p><code>setInterval()</code> 函数用于重复调用一个函数或执行代码片段，每隔指定的周期时间（以毫秒为单位）。<br />它也是非阻塞的，每次间隔时间到达后，就会尝试执行指定的代码。</p><h4 id="%E8%AF%AD%E6%B3%95-1" tabindex="-1">语法</h4><pre><code class="language-javascript">let intervalID = setInterval(function[, delay, arg1, arg2, ...]);</code></pre><ul><li><code>function</code>：要定期执行的函数。</li><li><code>delay</code>：执行间隔的时间，以毫秒为单位。</li><li><code>arg1, arg2, ...</code>：传递给函数的额外参数。</li></ul><h4 id="%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B-1" tabindex="-1">使用示例</h4><pre><code class="language-javascript">let counter = 0;const intervalID = setInterval(() =&gt; {  console.log(&quot;Hello World!&quot;);  counter++;  if (counter === 5) {    clearInterval(intervalID);  }}, 1000);</code></pre><p><em>这个例子会每秒打印 “Hello World!”，并在打印5次后停止</em></p><h3 id="cleartimeout()-%E5%92%8C-clearinterval()" tabindex="-1">clearTimeout() 和 clearInterval()</h3><p>这两个函数用于取消由 <code>setTimeout()</code> 和 <code>setInterval()</code> 设置的定时器。</p><h4 id="%E8%AF%AD%E6%B3%95-2" tabindex="-1">语法</h4><ul><li><code>clearTimeout(timeoutID)</code>：取消由 <code>setTimeout()</code> 设置的定时器。</li><li><code>clearInterval(intervalID)</code>：取消由 <code>setInterval()</code> 设置的定时器。</li></ul><h3 id="%E5%AE%9A%E6%97%B6%E5%99%A8%E5%87%BD%E6%95%B0%E7%9A%84%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F" tabindex="-1">定时器函数的使用注意</h3><p>虽然 <code>setTimeout()</code> 和 <code>setInterval()</code> 提供了方便的定时执行功能，但它们并不保证精确的时间控制。<br />JavaScript 是单线程的，如果事件队列中有其他任务在执行，定时器的回调可能会延迟执行。<br />此外，浏览器或者环境可能对这些函数的行为有特定的限制，如在后台标签页或未激活的窗口中降低定时器的精度或延迟执行，以优化性能和电池寿命。</p><h2 id="%E6%8B%93%E5%B1%95%E6%8F%90%E9%97%AE%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8%EF%BC%9Fvue%E4%B8%AD%E5%A6%82%E4%BD%95%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8%EF%BC%9Freact%E4%B8%AD%E5%A6%82%E4%BD%95%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8%EF%BC%9F" tabindex="-1">拓展提问：为什么要销毁定时器？Vue中如何销毁定时器？React中如何销毁定时器？</h2><p>在JavaScript中，销毁定时器是一个重要的操作，主要是为了避免不必要的资源占用和潜在的内存泄漏。定时器如果不被适当销毁，可能会导致一些问题，如：</p><ol><li><strong>继续执行不必要的操作</strong>：如果定时器触发的函数不再需要执行，定时器仍然活跃会导致额外的计算，这可能影响程序性能。</li><li><strong>内存泄漏</strong>：在某些情况下，定时器的回调函数可能引用了外部变量或者大型数据结构，如果定时器没有被销毁，这些引用关系可能导致所涉及的内存无法被垃圾回收，从而造成内存泄漏。</li></ol><h3 id="vue%E4%B8%AD%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8" tabindex="-1">Vue中销毁定时器</h3><p>在Vue中，通常我们会在组件的生命周期钩子中设置和销毁定时器。最常见的做法是在<code>mounted</code>钩子中创建定时器，并在<code>beforeDestroy</code>（Vue 2.x）或<code>beforeUnmount</code>（Vue 3.x）钩子中销毁定时器。例如：</p><pre><code class="language-javascript">export default {  mounted() {    this.timer = setInterval(() =&gt; {      console.log(&#39;Interval triggered&#39;);    }, 1000);  },  beforeDestroy() { // Vue 2.x    clearInterval(this.timer);  },  beforeUnmount() { // Vue 3.x    clearInterval(this.timer);  }}</code></pre><h3 id="react%E4%B8%AD%E9%94%80%E6%AF%81%E5%AE%9A%E6%97%B6%E5%99%A8" tabindex="-1">React中销毁定时器</h3><p>在React中，定时器通常在组件的生命周期方法或者钩子中设置和清除。使用类组件时，你可以在<code>componentDidMount</code>中设置定时器，并在<code>componentWillUnmount</code>中清除。如果使用函数组件和Hooks，可以在<code>useEffect</code>钩子中处理定时器：</p><pre><code class="language-js">import React, { useEffect } from &#39;react&#39;;function MyComponent() {  useEffect(() =&gt; {    const timer = setInterval(() =&gt; {      console.log(&#39;Interval triggered&#39;);    }, 1000);    // 清理函数    return () =&gt; clearInterval(timer);  }, []); // 空依赖数组表示这个effect只在组件挂载时运行一次  return &lt;div&gt;Check the console.&lt;/div&gt;;}</code></pre><p>在这个例子中，<code>useEffect</code>钩子的返回函数负责清除定时器，这个函数会在组件卸载时被调用，从而确保定时器被适当销毁。<br />通过这些方法，可以确保在组件或应用的生命周期结束时，相关的定时器也被正确清除，避免潜在的问题。</p><h2 id="%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF-requestanimationframe%3F" tabindex="-1">补充知识点：什么是 <code>requestAnimationFrame</code>?</h2><p><code>requestAnimationFrame</code> 是一个由浏览器提供的 API，用于在下一次浏览器重绘之前调用特定的函数，以执行动画或其他视觉更新。<br />这个函数是专门为动画和连续的视觉更新设计的，它可以帮助你创建平滑的动画效果，因为它能保证在浏览器进行下一次重绘之前更新动画帧。</p><h3 id="requestanimationframe-%E7%9A%84%E7%89%B9%E7%82%B9" tabindex="-1"><code>requestAnimationFrame</code> 的特点</h3><ol><li><strong>高效性能</strong>：<code>requestAnimationFrame</code> 会将动画函数的执行时机安排在浏览器的下一次重绘之前，这样可以保证动画的更新和浏览器的绘制操作同步进行，从而减少画面撕裂和不必要的计算和渲染，提高性能。</li><li><strong>节能</strong>：相比于 <code>setTimeout</code> 或 <code>setInterval</code>，<code>requestAnimationFrame</code> 是更智能的，因为它会在浏览器标签页不可见时自动暂停，从而减少CPU、GPU和电力的消耗。</li><li><strong>简单的使用方式</strong>：<code>requestAnimationFrame</code> 只需要一个回调函数作为参数，浏览器会自动计算出最适合的调用时间。</li></ol><h3 id="requestanimationframe-%E7%9A%84%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B" tabindex="-1"><code>requestAnimationFrame</code> 的使用示例</h3><p>假设你想要创建一个简单的动画，使一个元素在水平方向上移动：</p><pre><code class="language-javascript">let xPos = 0;function animate() {    xPos += 5; // 每帧向右移动5像素    element.style.transform = &#96;translateX(${xPos}px)&#96;; // 更新元素位置    if (xPos &lt; 500) { // 如果元素还没移动到500像素的位置，继续动画        requestAnimationFrame(animate);    }}requestAnimationFrame(animate); // 开始动画</code></pre><p>在这个示例中，<code>animate</code> 函数会被连续调用，每次调用都会将元素向右移动5像素，直到它达到500像素的位置。</p><h3 id="requestanimationframe-%E5%9C%A8%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%BA%94%E7%94%A8%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1"><code>requestAnimationFrame</code> 在工作中应用的注意事项</h3><ul><li><code>requestAnimationFrame</code> 需要在每一帧都重新调用来继续动画。</li><li>如果动画或者视觉更新不再需要，应当使用 <code>cancelAnimationFrame</code> 来取消回调函数的执行，避免不必要的性能消耗。</li><li>由于 <code>requestAnimationFrame</code> 的调用时间是由浏览器决定的，通常它的频率会与浏览器的刷新率相匹配，例如大多数设备上是每秒60次（即60Hz），但这可能会因设备而异。</li></ul><h2 id="%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF-setimmediate%3F" tabindex="-1">补充知识点：什么是 <code>setImmediate</code>?</h2><p><code>setImmediate</code> 是一个在 Node.js 环境中使用的函数，用于安排一个回调函数在当前事件循环结束后、下一次事件循环开始前被立即执行。<br />这个函数是特定于 Node.js 的，不是 Web 标准的一部分，因此在浏览器环境中不可用。</p><h3 id="setimmediate-%E7%9A%84%E5%8A%9F%E8%83%BD%E5%92%8C%E7%94%A8%E9%80%94" tabindex="-1"><code>setImmediate</code> 的功能和用途</h3><p><code>setImmediate</code> 的主要用途是将一些需要尽快执行但不必阻塞当前正在执行的操作的代码延迟执行。它与 <code>setTimeout</code> 和 <code>process.nextTick</code> 类似，但行为略有不同：</p><ul><li><code>setImmediate</code> 安排的任务会在当前事件循环的“check”阶段执行。</li><li><code>setTimeout(fn, 0)</code> 会在定时器阶段执行，通常会有一小段延迟（最小延迟时间，通常是1毫秒，取决于环境）。</li><li><code>process.nextTick</code> 会在当前事件循环的任何阶段结束后立即执行，甚至在进入下一个事件循环阶段之前。</li></ul><h3 id="setimmediate-%E7%9A%84%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B" tabindex="-1"><code>setImmediate</code> 的使用示例</h3><h4 id="%E4%B8%8B%E9%9D%A2%E6%98%AF%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84-node.js-%E7%A4%BA%E4%BE%8B%EF%BC%8C%E6%BC%94%E7%A4%BA%E4%BA%86-setimmediate-%E7%9A%84%E7%94%A8%E6%B3%95%EF%BC%9A" tabindex="-1">下面是一个简单的 Node.js 示例，演示了 <code>setImmediate</code> 的用法：</h4><pre><code class="language-javascript">console.log(&#39;开始执行&#39;);setImmediate(() =&gt; {    console.log(&#39;执行 setImmediate 回调&#39;);});console.log(&#39;结束执行&#39;);</code></pre><p><strong>在这个例子中，输出将会是：</strong></p><pre><code class="language-">开始执行结束执行执行 setImmediate 回调</code></pre><p><em>这表明 <code>setImmediate</code> 安排的回调确实是在当前事件循环的末尾执行的。</em></p><h3 id="setimmediate-%E5%9C%A8%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%BA%94%E7%94%A8%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1"><code>setImmediate</code> 在工作中应用的注意事项</h3><ul><li><strong>非标准 API</strong>：<code>setImmediate</code> 是一个非标准的 API，只在 Node.js 环境中可用。在浏览器中，你可能需要使用 <code>setTimeout(fn, 0)</code> 来达到类似的效果，虽然这两者在行为上有细微的差别。</li><li><strong>使用场景</strong>：通常用于处理长时间运行的操作后需要快速响应的场景，或者在处理完一些同步任务后需要尽快执行的异步代码。</li></ul><h2 id="%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF-process.nexttick%3F" tabindex="-1">补充知识点：什么是 <code>process.nextTick</code>?</h2><p><code>process.nextTick</code> 是 Node.js 环境中的一个函数，它用于在 Node.js 的事件循环的当前阶段完成后、下一个事件循环阶段开始之前，安排一个回调函数尽快执行。<br />这意味着无论在事件循环的哪个阶段调用 <code>process.nextTick</code>，提供的回调函数都会在当前操作完成后立即执行，但在任何I/O事件（包括定时器）或者执行其他计划任务之前执行。</p><h3 id="process.nexttick-%E7%9A%84%E5%8A%9F%E8%83%BD%E5%92%8C%E7%94%A8%E9%80%94" tabindex="-1"><code>process.nextTick</code> 的功能和用途</h3><p><code>process.nextTick</code> 主要用于确保在当前执行栈运行完毕后、在进行任何异步操作之前立即处理给定的回调。<br />这对于处理错误、清理资源或者在继续其他事件之前进行其他紧急计算是非常有用的。</p><h3 id="%E4%B8%8E-setimmediate-%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">与 <code>setImmediate</code> 的区别</h3><p>尽管 <code>process.nextTick</code> 和 <code>setImmediate</code> 都用于安排异步操作，但它们的执行时间点不同：</p><ul><li><code>process.nextTick</code> 回调在同一事件循环阶段尽可能早地执行，即在任何I/O事件和定时器之前。</li><li><code>setImmediate</code> 设计为在当前事件循环的所有I/O事件处理完毕后执行，即在下一个事件循环迭代的开始。</li></ul><h3 id="process.nexttick-%E7%9A%84%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B" tabindex="-1"><code>process.nextTick</code> 的使用示例</h3><p>下面是一个 Node.js 示例，展示了 <code>process.nextTick</code> 的使用：</p><pre><code class="language-javascript">console.log(&#39;开始执行&#39;);process.nextTick(() =&gt; {    console.log(&#39;执行 process.nextTick 回调&#39;);});console.log(&#39;结束执行&#39;);</code></pre><p><strong>在这个例子中，输出将会是：</strong></p><pre><code class="language-">开始执行结束执行执行 process.nextTick 回调</code></pre><p><em>这表明 <code>process.nextTick</code> 安排的回调确实是在当前事件循环的末尾、在其他异步事件之前执行的。</em></p><h3 id="process.nexttick-%E5%9C%A8%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%BA%94%E7%94%A8%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1"><code>process.nextTick</code> 在工作中应用的注意事项</h3><ul><li><strong>递归调用</strong>：如果 <code>process.nextTick</code> 被递归调用，或在一个循环中大量调用，它可以导致I/O饿死，因为它会在处理任何I/O事件之前不断地将新的回调加入到队列中。</li><li><strong>用途选择</strong>：<code>process.nextTick</code> 非常适合在当前操作完成后立即需要运行的情况，例如在事件或低级逻辑之后立即处理错误或进行清理。</li></ul><h3 id="%E6%A1%86%E6%9E%B6%E6%8B%93%E5%B1%95%EF%BC%9Avue-%E4%B8%AD%E6%9C%89%E7%94%A8%E5%88%B0-process.nexttick-%E5%90%97%EF%BC%9F" tabindex="-1">框架拓展：Vue 中有用到 <code>process.nextTick</code> 吗？</h3><p>Vue.js 中也使用了 <code>process.nextTick</code>，或者更具体地说，它使用了与之类似的异步延迟功能。<br /><code>process.nextTick</code> 是 Node.js 的一个特性，但在浏览器环境中，Vue 使用的是 <code>nextTick</code> 方法。<br />这是 Vue 的全局 API，用于在下一个 DOM 更新循环结束后执行延迟回调。<br />在内部，Vue 会尝试使用原生的 <code>Promise.then</code>、<code>MutationObserver</code>，或者 <code>setImmediate</code>，最后退回到 <code>setTimeout(fn, 0)</code>。</p><h4 id="vue%E4%B8%AD-nexttick-%E7%9A%84%E5%BA%94%E7%94%A8" tabindex="-1">Vue中 <code>nextTick</code> 的应用</h4><ol><li><strong>确保 DOM 更新完成</strong>：Vue 的数据绑定和 DOM 更新是异步的。当你更改数据后，DOM 不会立刻更新。<code>nextTick</code> 允许你在 DOM 更新完成后立即运行回调函数，这对于 DOM 依赖的操作非常有用。</li><li><strong>解决状态更新问题</strong>：有时候，你可能在同一方法中多次更改数据，使用 <code>nextTick</code> 可以确保所有的 DOM 更新都完成后再执行某些操作。</li></ol><h4 id="vue%E4%B8%AD-nexttick-%E7%9A%84%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B" tabindex="-1">Vue中 <code>nextTick</code> 的使用示例</h4><pre><code class="language-javascript">new Vue({  el: &#39;#app&#39;,  data: {    message: &#39;Hello&#39;  },  methods: {    updateMessage() {      this.message = &#39;Updated message&#39;;      this.$nextTick(() =&gt; {        // 这个回调将在 DOM 更新后执行// &#96;$nextTick()&#96; 用来确保 &#96;console.log(&#39;DOM updated&#39;)&#96; 的执行发生在 DOM 真正更新之后        console.log(&#39;DOM updated&#39;);      });    }  }});</code></pre><h2 id="%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF-mutationobserver%3F" tabindex="-1">补充知识点：什么是 <code>MutationObserver</code>?</h2><p><code>MutationObserver</code> 是一个强大的 Web API，用于监视 DOM（文档对象模型）的变化。<br />当 DOM 元素被添加、删除或修改时，<code>MutationObserver</code> 可以被用来异步地通知这些变化，使开发者能够响应这些变化并执行相应的操作。</p><h3 id="mutationobserver-%E7%9A%84%E5%8A%9F%E8%83%BD" tabindex="-1"><code>MutationObserver</code> 的功能</h3><p><code>MutationObserver</code> 主要用于监视以下类型的 DOM 变化：</p><ul><li>子节点的添加或删除。</li><li>属性的添加、删除或修改。</li><li>文本内容的变更。</li><li>更多其他类型的 DOM 变化。</li></ul><h3 id="mutationobserver-%E7%9A%84%E7%94%A8%E9%80%94" tabindex="-1"><code>MutationObserver</code> 的用途</h3><p>这使得 <code>MutationObserver</code> 在开发复杂的 Web 应用时非常有用，特别是在需要响应 DOM 变化来执行某些操作的情况下，如动态内容的加载、用户界面的自动更新等。</p><h3 id="%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-mutationobserver" tabindex="-1">如何使用 <code>MutationObserver</code></h3><p>要使用 <code>MutationObserver</code>，你需要创建一个观察者实例，定义一个回调函数来处理变化，然后指定要监视的 DOM 节点和具体的观察选项。</p><h3 id="mutationobserver-%E7%9A%84%E7%AE%80%E6%98%93%E7%A4%BA%E4%BE%8B" tabindex="-1"><code>MutationObserver</code> 的简易示例</h3><pre><code class="language-javascript">// 监视目标节点const targetNode = document.getElementById(&#39;some-id&#39;);// 配置观察选项:const config = { attributes: true, childList: true, subtree: true };// 当观察到变动时执行的回调函数const callback = function(mutationsList, observer) {    for(let mutation of mutationsList) {        if (mutation.type === &#39;childList&#39;) {            console.log(&#39;A child node has been added or removed.&#39;);        } else if (mutation.type === &#39;attributes&#39;) {            console.log(&#96;The ${mutation.attributeName} attribute was modified.&#96;);        }    }};// 创建一个观察者对象并传入回调函数const observer = new MutationObserver(callback);// 开始观察已配置的变动observer.observe(targetNode, config);// 之后，你可以停止观察// observer.disconnect();</code></pre><h3 id="mutationobserver-%E5%9C%A8%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%BA%94%E7%94%A8%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1"><code>MutationObserver</code> 在工作中应用的注意事项</h3><ul><li><strong>性能考虑</strong>：虽然 <code>MutationObserver</code> 是异步的，但过度使用或监视大量的 DOM 变化仍可能影响性能。合理配置观察选项，只监视必要的变化，可以帮助避免性能问题。</li><li><strong>内存管理</strong>：使用 <code>MutationObserver</code> 时应确保在不需要时断开观察（使用 <code>disconnect</code> 方法），以避免内存泄漏。</li></ul><h2 id="%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E5%90%88%E9%9B%86" tabindex="-1">面试问题合集</h2><p>恭喜你耐心看完本文了，对照下方的问题列表，自我提问一下吧~</p><blockquote><p>什么是 事件循环？<br />事件循环 的执行顺序是什么？<br />什么是 宏任务和微任务？<br />宏任务和微任务 有什么区别？<br />点击和键盘事件 是宏任务吗？<br />什么是 <code>Promise</code> 对象？<br />如何手写一个简易的 <code>Promise</code> 对象?<br />为什么 <code>Promise</code> 比 <code>setTimeout</code> 快？<br /><code>Promise.all</code> 和 <code>Promise.race</code> 有什么区别？<br />什么是 <code>requestAnimationFrame</code>?<br />什么是 <code>setImmediate</code>?<br />什么是 <code>process.nextTick</code>?<br />Vue 中有用到 <code>process.nextTick</code> 吗？<br />什么是 <code>MutationObserver</code>?<br />Vue中如何销毁定时器？React中如何销毁定时器？为什么要销毁定时器？</p></blockquote><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJs/blob/master/js-blog/2024/2024-05/js-event-loop-job.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[SQL练习之打卡记录数据统计问题]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/sql-practice-punch" />
                <id>tag:https://fx67ll.xyz,2024-05-08:sql-practice-punch</id>
                <published>2024-05-08T17:55:29+08:00</published>
                <updated>2025-07-02T11:29:42+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>最近老婆的公司，关闭了OA系统中，各类打卡时间数据统计的功能，为了不麻烦老婆手算，就做了一个简单的打卡系统，方便自动统计老婆想要知道的各类数据。</p><p>做的过程中就遇到了几个还挺有意思的SQL，这里写成一篇博文，方便后期练习~</p><p><em>Tip：需要答案的盆友可以访问 <a href="https://fx67ll.xyz/archives/sql-practice-punch-answer" target="_blank">参考答案的链接</a>，密码是<code>123456</code>~</em></p><h3 id="%E5%BB%BA%E8%A1%A8%E8%AF%AD%E5%8F%A5" tabindex="-1">建表语句</h3><pre><code class="language-sql">drop table if exists fx67ll_punch_log;create table fx67ll_punch_log (  punch_id             bigint(20)      not null auto_increment    comment &#39;打卡记录主键&#39;,  punch_type           char(1)                                    comment &#39;打卡类型（1代表上班 2代表下班）&#39;,  punch_remark         varchar(1023)   default &#39;&#39;                 comment &#39;打卡记录备注&#39;,  del_flag             char(1)         default &#39;0&#39;                comment &#39;删除标志（0代表存在 2代表删除）&#39;,  user_id              bigint(20)                                 comment &#39;用户ID&#39;,  create_by            varchar(64)     default &#39;&#39;                 comment &#39;记录创建者&#39;,  create_time        datetime                                   comment &#39;记录创建时间&#39;,  update_by            varchar(64)     default &#39;&#39;                 comment &#39;记录更新者&#39;,  update_time          datetime                                   comment &#39;记录更新时间&#39;,  primary key (punch_id)) engine=innodb auto_increment=1 comment = &#39;打卡记录表&#39;;</code></pre><h3 id="%E6%8F%92%E5%85%A5%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE" tabindex="-1">插入测试数据</h3><pre><code class="language-sql">INSERT INTO &#96;ruoyi-mysql&#96;.fx67ll_punch_log (punch_type,punch_remark,del_flag,user_id,create_by,create_time,update_by,update_time) VALUES (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-06 00:00:00&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-06 21:00:00&#39;), (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-08 15:00:00&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-08 18:19:00&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-08 21:00:00&#39;), (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-10 20:50:00&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-10 21:44:00&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-10 22:50:00&#39;), (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-11 10:00:00&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-12 19:49:41&#39;,&#39;fx67ll&#39;,&#39;2023-12-11 20:00:00&#39;), (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,101,&#39;user&#39;,&#39;2023-12-13 17:03:22&#39;,&#39;user&#39;,&#39;2023-12-13 00:06:00&#39;), (&#39;2&#39;,&#39;e e e&#39;,&#39;0&#39;,101,&#39;user&#39;,&#39;2023-12-13 17:03:14&#39;,&#39;user&#39;,&#39;2023-12-11 01:01:00&#39;), (&#39;1&#39;,&#39;123&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2023-12-14 09:53:54&#39;,&#39;fx67ll&#39;,&#39;2023-12-14 09:53:50&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,101,&#39;user&#39;,&#39;2024-03-13 17:49:16&#39;,&#39;user&#39;,&#39;2024-03-13 17:49:00&#39;), (&#39;1&#39;,&#39;324&#39;,&#39;0&#39;,101,&#39;user&#39;,&#39;2024-03-21 11:22:16&#39;,&#39;user&#39;,&#39;2024-03-21 11:22:17&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,101,&#39;user&#39;,&#39;2024-03-21 11:22:43&#39;,&#39;user&#39;,&#39;2024-03-21 22:22:39&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2024-03-30 20:01:10&#39;,&#39;fx67ll&#39;,&#39;2024-03-30 20:01:00&#39;), (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2024-04-30 15:01:16&#39;,&#39;fx67ll&#39;,&#39;2024-04-30 00:01:06&#39;), (&#39;2&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2024-04-30 15:01:25&#39;,&#39;fx67ll&#39;,&#39;2024-04-30 23:01:16&#39;), (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,1,&#39;fx67ll&#39;,&#39;2024-04-30 15:01:31&#39;,&#39;fx67ll&#39;,&#39;2024-04-24 15:01:25&#39;), (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,101,&#39;user&#39;,&#39;2024-05-03 02:39:33&#39;,&#39;user&#39;,&#39;2024-05-03 02:39:29&#39;), (&#39;1&#39;,&#39;&#39;,&#39;0&#39;,101,&#39;user&#39;,&#39;2024-05-03 02:39:41&#39;,&#39;user&#39;,&#39;2024-05-04 00:39:33&#39;), (&#39;2&#39;,&#39;123&#39;,&#39;0&#39;,101,&#39;user&#39;,&#39;2024-05-03 02:39:52&#39;,&#39;user&#39;,&#39;2024-05-04 23:39:41&#39;);</code></pre><h2 id="%E9%97%AE%E9%A2%98%E4%B8%80%EF%BC%9A%E7%BB%9F%E8%AE%A1%E6%AF%8F%E4%B8%AA%E7%94%A8%E6%88%B7%E6%AF%8F%E4%B8%AA%E6%9C%88%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%80%BB%E6%97%B6%E9%95%BF%E3%80%81%E6%80%BB%E6%89%93%E5%8D%A1%E5%A4%A9%E6%95%B0%E3%80%81%E6%9C%89%E6%95%88%E6%89%93%E5%8D%A1%E5%A4%A9%E6%95%B0%E4%BB%A5%E5%8F%8A%E6%97%A5%E5%9D%87%E5%B7%A5%E6%97%B6" tabindex="-1">问题一：统计每个用户每个月的工作总时长、总打卡天数、有效打卡天数以及日均工时</h2><h4 id="%E9%9C%80%E8%A6%81%E5%BE%97%E5%88%B0%E5%A6%82%E4%B8%8B%E7%9A%84%E7%BB%9F%E8%AE%A1%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%AF%8F%E4%B8%AA%E5%AD%97%E6%AE%B5%E7%9A%84%E5%90%AB%E4%B9%89%E5%88%86%E5%88%AB%E6%98%AF%EF%BC%9A" tabindex="-1">需要得到如下的统计数据，每个字段的含义分别是：</h4><ol><li><code>punch_user</code>：打卡的用户</li><li><code>punch_month</code>：本条记录统计的打卡月份</li><li><code>total_work_hours</code>：当月的工作总时长，小时为单位</li><li><code>total_work_minutes</code>：当月的工作总时长，分钟为单位</li><li><code>total_work_seconds</code>：当月的工作总时长，秒为单位</li><li><code>total_punch_days</code>：当月的总打卡天数，只要有打卡记录就算，可能只打了上班卡，或者只打了下班卡，但是，没有打卡记录的天数则不算</li><li><code>total_work_days</code>：当月的有效打卡天数，必须满足条件，既有上班打卡记录，又有下班打卡记录，才算一个有效打卡天数</li><li><code>work_hours_per_day</code>：当月的日均工时，小时为单位</li></ol><pre><code class="language-">punch_user|punch_month|total_work_hours|total_work_minutes|total_work_seconds|total_punch_days|total_work_days|work_hours_per_day|----------+-----------+----------------+------------------+------------------+----------------+---------------+------------------+fx67ll    |2023-12    |         39.0000|              2340|            140400|               5|              4|        9.75000000|fx67ll    |2024-03    |                |                  |                  |               1|              0|                  |fx67ll    |2024-04    |         23.0028|              1380|             82810|               2|              1|       23.00277778|user      |2023-12    |                |                  |                  |               2|              0|                  |user      |2024-03    |         11.0061|               660|             39622|               2|              1|       11.00611111|user      |2024-05    |         23.0022|              1380|             82808|               2|              1|       23.00222222|</code></pre><h2 id="%E9%97%AE%E9%A2%98%E4%BA%8C%EF%BC%9A%E7%BB%9F%E8%AE%A1%E6%AF%8F%E4%B8%AA%E7%94%A8%E6%88%B7%E5%BD%93%E6%9C%88%E7%9A%84%E5%8F%AA%E6%89%93%E4%BA%86%E4%B8%80%E6%AC%A1%E5%8D%A1%E7%9A%84%E7%BC%BA%E5%8D%A1%E8%AE%B0%E5%BD%95" tabindex="-1">问题二：统计每个用户当月的只打了一次卡的缺卡记录</h2><h4 id="%E9%9C%80%E8%A6%81%E5%BE%97%E5%88%B0%E5%A6%82%E4%B8%8B%E7%9A%84%E7%BB%9F%E8%AE%A1%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%AF%8F%E4%B8%AA%E5%AD%97%E6%AE%B5%E7%9A%84%E5%90%AB%E4%B9%89%E5%88%86%E5%88%AB%E6%98%AF%EF%BC%9A-1" tabindex="-1">需要得到如下的统计数据，每个字段的含义分别是：</h4><ol><li><code>punch_user</code>：缺卡的用户</li><li><code>punch_month</code>：本条记录的缺卡月份</li><li><code>punch_day</code>：本条记录的缺卡日期</li><li><code>lost_punch_type</code>：本条记录的缺卡类型，需要输出是上班缺卡还是下班缺卡</li></ol><pre><code class="language-">punch_user|punch_month|punch_day |lost_punch_type|----------+-----------+----------+---------------+fx67ll    |2023-12    |2023-12-14|下班缺卡           |fx67ll    |2024-03    |2024-03-30|上班缺卡           |fx67ll    |2024-04    |2024-04-24|下班缺卡           |user      |2023-12    |2023-12-11|上班缺卡           |user      |2023-12    |2023-12-13|下班缺卡           |user      |2024-03    |2024-03-13|上班缺卡           |user      |2024-05    |2024-05-03|下班缺卡           |</code></pre><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2024/2024-05/sql-practice-punch.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[前端性能优化的利器 ——— 浅谈JavaScript中的防抖和节流]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/js-debounce-throttle" />
                <id>tag:https://fx67ll.xyz,2023-08-03:js-debounce-throttle</id>
                <published>2023-08-03T16:29:38+08:00</published>
                <updated>2025-07-02T11:31:39+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<blockquote><p>防抖和节流函数是工作中两种常用的前端性能优化函数，今天我就来总结一下什么是防抖和节流，并详细说明一下如何在工作中应用防抖和节流函数</p></blockquote><h2 id="%E4%BB%80%E4%B9%88%E6%98%AF%E9%98%B2%E6%8A%96%E5%92%8C%E8%8A%82%E6%B5%81%3F" tabindex="-1">什么是防抖和节流?</h2><p>在 JavaScript 中，防抖（debounce）和节流（throttle）是用来限制函数执行频率的两种常见技术。</p><p><strong>防抖（debounce）</strong> 是指在某个时间段内，只执行最后一次触发的函数调用。如果在这个时间段内再次触发该函数，会重新计时，直到等待时间结束才会执行函数。<br />这个技术通常用于处理频繁触发的事件，比如窗口大小调整、搜索框输入等。防抖可以避免函数执行过多次，以减少网络开销和性能负担。</p><p><strong>节流（throttle）</strong> 是指在一段时间内限制函数的执行频率，保证一定时间内只执行一次函数调用。无论触发频率多高，都会在指定时间间隔内执行一次函数。<br />这个技术通常用于处理连续触发的事件，比如滚动事件、鼠标移动事件等。节流可以控制函数的执行频率，以减少资源消耗和提高性能。</p><h3 id="%E6%89%8B%E5%86%99%E4%B8%80%E4%B8%AA%E9%98%B2%E6%8A%96%E7%9A%84%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0" tabindex="-1">手写一个防抖的工具函数</h3><pre><code class="language-">function debounce(func, delay) {  let timeoutId;    return function() {    const context = this;    const args = arguments;        clearTimeout(timeoutId);        timeoutId = setTimeout(function() {      func.apply(context, args);    }, delay);  };}</code></pre><h4 id="%E5%87%BD%E6%95%B0%E8%AF%B4%E6%98%8E" tabindex="-1">函数说明</h4><ol><li>这个防抖函数接受两个参数：<code>func</code>表示需要进行防抖的函数，<code>delay</code>表示延迟的时间间隔（以毫秒为单位）</li><li>函数内部使用了一个<code>timeoutId</code>变量来保存定时器的标识。当调用防抖函数返回的新函数时，会清除之前的定时器，并设置一个新的定时器。只有在延迟时间内没有再次调用该新函数时，才会触发最终的函数执行</li></ol><h4 id="%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B" tabindex="-1">使用示例</h4><p>该示例表示在全局滚动事件中使用防抖函数，每200毫秒内如果触发滚动事件，那么不会执行<code>handleScroll()</code>函数。然后重新计时200毫秒，再次判断，直到最后一个200毫秒内没有触发滚动事件，才会执行<code>handleScroll()</code>函数</p><pre><code class="language-">function handleScroll() {  console.log(&#39;Scrolled&#39;);}const debouncedScroll = debounce(handleScroll, 200);window.addEventListener(&#39;scroll&#39;, debouncedScroll);</code></pre><h3 id="%E6%89%8B%E5%86%99%E4%B8%80%E4%B8%AA%E8%8A%82%E6%B5%81%E7%9A%84%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0" tabindex="-1">手写一个节流的工具函数</h3><pre><code class="language-">function throttle(func, delay) {  let timeoutId;  let lastExecTime = 0;  return function(...args) {    const currentTime = Date.now();    const remainingTime = delay - (currentTime - lastExecTime);    clearTimeout(timeoutId);    if (remainingTime &lt;= 0) {      func.apply(this, args);      lastExecTime = currentTime;    } else {      timeoutId = setTimeout(() =&gt; {        func.apply(this, args);        lastExecTime = Date.now();      }, remainingTime);    }  };}</code></pre><h4 id="%E5%87%BD%E6%95%B0%E8%AF%B4%E6%98%8E-1" tabindex="-1">函数说明</h4><ol><li>这个节流函数接受两个参数：<code>func</code>是要执行的函数，<code>delay</code>是延迟时间（以毫秒为单位）</li><li>它返回一个新的函数，该函数在调用时会根据指定的延迟时间来限制原始函数的执行频率</li></ol><h4 id="%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B-1" tabindex="-1">使用示例</h4><p>在全局滚动事件中使用节流函数，无论在滚动事件的监听过程中，触发了几次<code>handleScroll()</code>函数，都只会在每200毫秒内执行一次<code>handleScroll()</code>函数。</p><pre><code class="language-">function handleScroll() {  console.log(&#39;Scrolled&#39;);}const throttledScroll = throttle(handleScroll, 200);window.addEventListener(&#39;scroll&#39;, throttledScroll);</code></pre><h2 id="%E5%A6%82%E4%BD%95%E5%9C%A8%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%BA%94%E7%94%A8%E9%98%B2%E6%8A%96%E5%92%8C%E8%8A%82%E6%B5%81" tabindex="-1">如何在工作中应用防抖和节流</h2><blockquote><p>防抖和节流主要应用于：搜索框输入事件监听、窗口大小调整事件监听、按钮点击事件监听、滚动事件监听、鼠标移动事件监听等等场景。</p></blockquote><h3 id="%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%93%AA%E4%BA%9B%E5%9C%BA%E6%99%AF%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E9%98%B2%E6%8A%96%E5%87%BD%E6%95%B0%EF%BC%9F" tabindex="-1">工作中哪些场景可以使用防抖函数？</h3><ol><li><strong>用户输入：</strong> 当用户在表单输入框中频繁输入时，可以使用防抖函数来延迟处理用户输入，避免频繁的请求或操作，提高性能和用户体验。</li><li><strong>搜索框：</strong> 在搜索框中，当用户连续输入关键字时，可以使用防抖函数来延迟发送搜索请求，以避免请求过多。</li><li><strong>窗口调整：</strong> 当窗口大小调整时，会触发resize事件，可以使用防抖函数来限制resize事件的触发次数，避免频繁执行调整相关的代码。</li><li><strong>按钮频繁点击：</strong> 当按钮被频繁点击时，可以使用防抖函数来限制按钮点击的触发次数。</li></ol><h3 id="%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%93%AA%E4%BA%9B%E5%9C%BA%E6%99%AF%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E8%8A%82%E6%B5%81%E5%87%BD%E6%95%B0%EF%BC%9F" tabindex="-1">工作中哪些场景可以使用节流函数？</h3><ol><li><strong>用户输入：</strong> 当用户在文本框中输入时，触发搜索功能。使用节流函数可以限制搜索请求的频率，以避免频繁的网络请求。例如，可以设置一个定时器，在用户输入后的一小段时间内不触发搜索请求，只在定时器结束后才进行搜索。</li><li><strong>无限加载：</strong> 当用户滚动页面时，触发加载更多数据的操作。使用节流函数可以限制加载操作的频率，以提高页面的响应性能。例如，可以设置一个定时器，在用户滚动过程中只触发加载操作的最后一次滚动事件。</li><li><strong>按钮频繁点击：</strong> 当用户频繁点击某个按钮时，触发某个操作。使用节流函数可以限制点击操作的频率，以避免重复操作或者混乱的界面状态。例如，可以设置一个定时器，在用户点击后的一小段时间内不触发重复操作。</li></ol><h3 id="%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8loadsh.js%E5%B7%A5%E5%85%B7%E5%BA%93%E4%B8%AD%E7%9A%84%E9%98%B2%E6%8A%96%E5%92%8C%E8%8A%82%E6%B5%81%E5%87%BD%E6%95%B0" tabindex="-1">如何使用<code>loadsh.js</code>工具库中的防抖和节流函数</h3><p>实际开发过程中，我们的项目中可能会直接使用<code>loadsh.js</code>工具库，来避免重复造轮子，所以这里也特地说明一下如何使用<code>loadsh.js</code>工具库中的防抖和节流函数</p><ol><li>安装<code>loadsh.js</code>工具库</li></ol><pre><code class="language-">npm install lodash  </code></pre><ol start="2"><li>在项目中引入<code>loadsh.js</code>工具库，不同前端项目引入方式不同，请自行鉴别</li></ol><pre><code class="language-">import { debounce, throttle } from &#39;loadsh&#39;;</code></pre><ol start="3"><li>使用防抖函数，该示例中，<code>submitData()</code>函数被限制为每1秒只会执行最后一次，如果在等待时间内多次调用该函数，则会重置1秒的等待时间</li></ol><pre><code class="language-">// 定义要延迟执行的函数function submitData(data) {  console.log(&#39;保存数据：&#39;, data);}// 使用debounce函数创建一个延迟执行的函数const debouncedFn = _.debounce(submitData, 1000);// 模拟连续触发保存数据的操作// 等待1秒后，只会执行最后一次保存数据的操作debouncedFn(&#39;数据1&#39;); // 不会输出debouncedFn(&#39;数据2&#39;); // 不会输出debouncedFn(&#39;数据3&#39;); //  输出 —— 保存数据：数据3</code></pre><ol start="4"><li>使用节流函数，该示例中，<code>throttledFn()</code>函数被限制为每秒只能执行一次，如果在等待时间内多次调用该函数，则不会执行</li></ol><pre><code class="language-">// 定义要减少调用次数的函数function fetchData(data) {console.log(&#39;拉取数据:&#39;, data);}// 使用throttle函数创建一个定时执行的函数const throttledFn = _.throttle(fetchData, 2000);// 调用throttledFn函数throttledFn(&#39;数据1&#39;); // 输出 —— 拉取数据: 数据1// 在1秒内多次调用throttledFn函数throttledFn(&#39;数据2&#39;); // 不会输出// 2秒后再次调用throttledFn函数setTimeout(() =&gt; {  throttledFn(&#39;数据3&#39;); // 输出 —— 拉取数据: 数据3}, 1000);</code></pre><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJs/blob/master/js-blog/2023/2023-08/js-debounce-throttle.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[解决 Https 站点请求 Http 接口服务后报 the content must be served over HTTPS 错误的问题]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/nginx-https-api" />
                <id>tag:https://fx67ll.xyz,2023-07-18:nginx-https-api</id>
                <published>2023-07-18T10:28:25+08:00</published>
                <updated>2025-07-03T10:39:28+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90" tabindex="-1">问题分析</h3><p>之前将自己所有的 <code>Http</code> 站点全部更新为 <code>Https</code> 站点，但是在请求后台接口服务的时候还是 <code>Http</code> 请求，导致部署之后，直接在控制台报 <code>This request has been blocked; the content must be served over HTTPS;</code> 的错误</p><h3 id="%E8%A7%A3%E5%86%B3%E6%80%9D%E8%B7%AF" tabindex="-1">解决思路</h3><p>因为我不想耗费精力，将所有的后台接口服务也更新为支持 <code>Https</code> 请求，所以访问了一些资料之后，发现了一个非常巧妙的思路，省时省力解决这个问题。</p><p>那就是直接使用Nginx将后台接口服务的 <code>http</code> 请求地址代理到前端 <code>Https</code> 站点的一个目录下，经过Nginx这一层将后台接口服务的 <code>Http</code> 请求包装成 <code>Https</code> 请求</p><h3 id="%E4%B8%BE%E4%B8%AA%E6%A0%97%E5%AD%90" tabindex="-1">举个栗子</h3><ol><li>比如你之前的后台接口服务请求地址是：<code>http://bbb.com</code>（甚至你的请求地址是<code>ip+端口</code>都没关系）</li><li>然后你的前端站点部署的域名是：<code>https://aaa.com</code></li><li>你可以通过修改Nginx配置，将后台接口服务的地址转发到前端域名地址的一个目录下，比如：<code>https://aaa.com/bbb-api</code></li><li>这样就可以让Nginx帮你完美解决 <code>http</code> 请求无法访问 <code>https</code> 站点的问题~~~牛逼o(￣▽￣)ｄ</li></ol><h3 id="nginx%E9%85%8D%E7%BD%AE%E7%A4%BA%E4%BE%8B" tabindex="-1">Nginx配置示例</h3><p>这里没有放出全部的配置示例，因为只需要修改前端域名中443端口的监听配置即可</p><pre><code class="language-">server{listen 443 ssl;server_name aaa.com;ssl_certificate               /etc/ssl/aaa.com.crt;ssl_certificate_key           /etc/ssl/aaa.com.key;ssl_protocols                 TLSv1.1 TLSv1.2 TLSv1.3;ssl_ciphers                   ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;ssl_prefer_server_ciphers     on;ssl_session_cache             shared:SSL:1m;ssl_session_timeout           10m;# 这里是前端https站点的配置location / {proxy_pass http://ip:端口;}# 这里是转发到另一个后台http请求的配置，也可以直接用ip+端口location /bbb-api/ {proxy_pass http://bbb.com/;}}</code></pre><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/blob/master/serve-blog/2023/2023-03/nginx-api-https.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Linux 运维常用命令]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/linux-shell" />
                <id>tag:https://fx67ll.xyz,2023-07-15:linux-shell</id>
                <published>2023-07-15T10:57:04+08:00</published>
                <updated>2025-07-03T10:39:34+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h4 id="%E5%85%88%E8%AF%B4%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说些废话</h4><p>因为笔者有着大量运维部署站点的需求，所以之前一直在陆陆续续学习并实践各种<code>Linux</code>的<code>Shell</code>命令，在此记录一些高频命令的使用的说明，方便自己回顾加深记忆</p><p>后期如果有用到一些新的命令，也会继续保持更新，如果写的有不对的地方，也请各位看官指出，非常感谢~</p><h3 id="%E7%B3%BB%E7%BB%9F%E4%BF%A1%E6%81%AF%E6%9F%A5%E8%AF%A2%E7%9A%84%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4" tabindex="-1">系统信息查询的相关命令</h3><ol><li><code>df</code> 获取有关系统磁盘空间使用情况的报告<ul><li><code>-h / -hg</code>  以 GB 为单位显示</li><li><code>-m / -hm</code>  以 MB 为单位显示</li><li><code>-k / -hk</code>  以 KB 为单位显示，默认单位</li></ul></li><li><code>du</code> 显示指定的目录或文件所占用的磁盘空间<ul><li>同上</li></ul></li><li><code>jobs</code> 显示所有当前作业及其状态</li><li><code>hostname</code> 显示主机/网络的名称</li><li><code>hostname -i</code> 显示主机/网络的IP</li><li><code>uname</code> 显示系统名称</li><li><code>ping</code> 检查与服务器的连接状态</li></ol><h3 id="%E8%BF%9B%E7%A8%8B%E6%9F%A5%E8%AF%A2%E4%BB%A5%E5%8F%8A%E6%93%8D%E4%BD%9C%E7%9A%84%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4" tabindex="-1">进程查询以及操作的相关命令</h3><ol><li><code>top</code> 查看所有进程<ul><li>以下是一些常用的高频操作</li><li>运行<code>top</code>命令后，按大写的<code>M</code>，按内存使用率排序</li><li>运行<code>top</code>命令后，按大写的<code>P</code>，按CPU使用率排序</li><li>运行<code>top</code>命令后，按两下大写的<code>E</code>，将上方明细里的<code>KiB</code>转换成<code>GiB</code>展示，小写的<code>e</code>转换列表里的大小单位</li></ul></li><li><code>top -u (需要查询的用户名)</code> 查看某个linux用户下所有进程的信息</li><li><code>top -p (需要查询的进程ID)</code> 查看某个特定ID进程的信息</li><li><code>kill -9 (需要杀死的进程ID)</code> 杀死执行进程</li><li><code>yum(Yellow dog Updater)</code> 命令的全程是是一个在 Fedora 和 RedHat 以及 SUSE 中的 Shell 前端软件包管理器</li><li><code>ps -ef | grep (需要查询的服务名)</code> 查询特定服务进程信息</li></ol><h4 id="%E5%85%B3%E4%BA%8Etop%E5%91%BD%E4%BB%A4%E5%85%B6%E4%BB%96%E5%8F%AF%E9%80%89%E5%8F%82%E6%95%B0" tabindex="-1">关于top命令其他可选参数</h4><pre><code class="language-">M： 根据驻留内存大小进行排序  P： 根据CPU使用百分比大小进行排序  T： 根据时间/累计时间进行排序  q： 退出程序  l： 切换显示平均负载和启动时间信息  m： 切换显示内存信息  t： 切换显示进程和CPU状态信息  c： 切换显示命令名称和完整命令行  1： 数字 1 显示各个CPU使用情况  </code></pre><h3 id="%E8%BF%9B%E5%85%A5%E6%9F%A5%E7%9C%8B%E7%9B%AE%E5%BD%95%E7%9A%84%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4" tabindex="-1">进入查看目录的相关命令</h3><ol><li><code>pwd</code> 该命令将返回一个绝对路径</li><li><code>cd</code> 浏览指定目录（<em>Linux 的 Shell 是区分大小写的。因此，您必须准确输入名称的目录</em>）</li><li><code>cd ..</code> 返回上一级</li><li><code>ls</code> 查看当前目录的内容</li><li><code>ls 目录路径</code> 查看指定目录的内容</li></ol><h3 id="%E6%9D%83%E9%99%90%E6%93%8D%E4%BD%9C%E7%9A%84%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4" tabindex="-1">权限操作的相关命令</h3><ol><li><code>sudo</code> 该命令是<strong>SuperUser Do</strong>的缩写，使您能够执行需要管理或超级用户权限的任务（<em>建议不要将此命令用于日常使用，因为如果您做错了一些事情，很容易发生错误</em>）</li><li><code>chmod</code> 更改文件和目录的读取，写入和执行权限（<em>请仔细阅读</em><a href="https://www.runoob.com/linux/linux-comm-chmod.html" target="_blank">教程</a><em>后使用</em>）</li><li><code>chown</code> 更改文件的所有权转让给指定的用户名</li></ol><h3 id="%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E7%9A%84%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4" tabindex="-1">文件操作的相关命令</h3><p><em>笔者更习惯在 xftp 中可视化操作文件，更加安全方便直观，对于一些无法显示的配置文件才会用到相关操作命令</em></p><ol><li><code>cp</code> 将文件从当前目录复制到另一个目录</li><li><code>mv</code> 将文件从当前目录移动到另一个目录</li><li><code>tar</code> 归档多个文件到一个压缩包（<em>注意！！！此命令需要配合其他参数使用，请仔细阅读</em><a href="https://blog.csdn.net/lmjssjj/article/details/129275081" target="_blank">教程</a><em>后使用</em>）</li><li><code>mkdir</code> 创建一个新目录</li><li><code>rmdir</code> 删除目录，仅允许删除空目录</li><li><code>rm</code> 删除目录以及其中的内容（<em>注意：使用此命令时要格外小心，并仔细检查您所在的目录，这将删除所有内容，并且没有撤消操作</em>）</li><li><code>rm -r</code> 删除目录，作为<code>rmdir</code>的替代方法，但是目录还有目录的话用<code>-r</code>是删除不了的</li><li><code>rm -R</code> 删除目录以及其子目录</li><li><code>rm -rf</code> 无提示地强制递归删除文件，<code>-f</code>的作用是不再询问确定删除（<em>注意：使用此命令时要格外小心，并仔细检查您所在的目录，这将删除所有内容，并且没有撤消操作</em>）</li><li><code>touch</code> 创建新的空白文件</li><li><code>locate</code> 定位文件</li><li><code>locate -i</code> 不区分大小写定位文件</li><li><code>find</code> 类似定位命令，能搜索文件和目录。区别在于，您可以使用<code>find</code>命令在给定目录中查找文件</li></ol><h2 id="%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" tabindex="-1">参考资料</h2><ol><li><a href="https://www.runoob.com/linux/linux-command-manual.html" target="_blank">Linux 命令大全</a></li><li><a href="https://www.wangan.com/wenda/4462" target="_blank">Linux 常用命令有哪些</a></li><li><a href="https://www.runoob.com/linux/linux-comm-chmod.html" target="_blank">Linux chmod命令</a></li><li><a href="https://www.cnblogs.com/hls-code/p/16692397.html" target="_blank">Linux操作系统之rm命令详解</a></li><li><a href="https://blog.csdn.net/ATTAIN__/article/details/124730900" target="_blank">tar命令详解</a></li><li><a href="https://blog.csdn.net/ATTAIN__/article/details/124730900" target="_blank">tar命令的讲解与使用</a></li><li><a href="https://www.cnblogs.com/wangzy-Zj/p/16869149.html" target="_blank">top命令按内存/CPU进行排序</a></li><li><a href="http://c.biancheng.net/view/1068.html" target="_blank">Linux kill命令详解：终止进程</a></li><li><a href="https://baijiahao.baidu.com/s?id=1761262711357522677&amp;wfr=spider&amp;for=pc" target="_blank">Linux下查看某一进程所占用内存和CPU的方法</a></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/blob/master/serve-blog/2023/2023-07/linux-shell.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[详解在Linux中修改Tomcat使用的jdk版本]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/tomcat-linux-jdk-version" />
                <id>tag:https://fx67ll.xyz,2023-07-13:tomcat-linux-jdk-version</id>
                <published>2023-07-13T19:39:57+08:00</published>
                <updated>2025-07-03T10:39:40+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90" tabindex="-1">问题分析</h3><ol><li>由于部署个人项目使用了<code>openjdk11</code>，但是我之前安装的是<code>jdk1.8</code>，jdk版本升级的后果就是，tomcat运行的时候报一点小bug（因为之前安装tomcat默认使用了系统的jdk版本）</li><li>所以就想着把tomcat使用的jdk版本调回原来的，找了很多资料之后，决定在tomcat的运行文件中覆盖使用的jdk版本路径</li><li>需要注意的是，<code>openjdk11</code>没有jre目录，所以一定要注意将<code>JRE_HOME=$JAVA_HOME/jre</code>中的jre删掉</li></ol><h3 id="%E8%AF%A6%E7%BB%86%E6%AD%A5%E9%AA%A4" tabindex="-1">详细步骤</h3><ol><li>检查tomcat安装目录下<code>/bin/catalina.sh</code>和<code>/bin/setclasspath.sh</code>文件</li><li>分别在这两个文件的头部添加以下局部变量覆盖系统环境变量<blockquote><p><code>export JAVA_HOME=/usr/soft/install/jdk1.8.0_144</code><br /><code>export JRE_HOME=$JAVA_HOME/</code><br /><code>export PATH=$PATH:$JAVA_HOME/bin</code></p></blockquote></li><li>两个文件修改完成之后，在<code>bin</code>目录下执行<code>./version.sh</code>，会打印出来<code>jdk版本</code></li><li>还有点需要注意的是，tomcat如果运行不成功或者运行多个之后，可能会出现访问的问题，不要重复开重复关，检查配置都没有问题之后再去打开<blockquote><p>查看tomcat是否在运行 <code>ps -ef |grep tomcat</code><br />如果在运行，可以杀掉进程之后再重启 <code>kill -9 pid  # pid为相应的进程号</code></p></blockquote></li><li>最后再总结一下关闭和开启tomcat的命令，均在bin目录下执行<blockquote><p>开启 <code>./startup.sh</code><br />关闭 <code>./shutdown.sh</code></p></blockquote></li></ol><h3 id="%E5%A6%82%E4%BD%95%E4%BF%AE%E5%A4%8Dlinux%E7%9B%B8%E5%85%B3%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84%E9%9D%9E%E6%B3%95%E5%AD%97%E7%AC%A6%5Em" tabindex="-1">如何修复linux相关配置文件中的非法字符<code>^M</code></h3><ol><li>因为tomcat的配置文件过长，直接在linux中使用<code>vim</code>命令修改属实比较痛苦，笔者直接将文件用 xftp 拉下来，直接在编辑器中修改，但是会导致文件中出现非法字符<code>^M</code></li><li><code>^M</code>是windows下的断元字符，在linux中无法识别</li><li>可以通过安装linux工具来处理，按顺序执行以下命令即可<ul><li>安装工具包 <code>yum install -y dos2unix</code></li><li>格式化文档 <code>dos2unix (需要格式化的文档地址)</code></li></ul></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/blob/master/serve-blog/2023/2023-07/tomcat-linux-jdk-version.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[js数组操作——对象数组根据某个相同的字段分组]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/array-handle-group" />
                <id>tag:https://fx67ll.xyz,2022-11-03:array-handle-group</id>
                <published>2022-11-03T19:38:57+08:00</published>
                <updated>2025-07-02T11:34:55+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E5%85%88%E8%AF%B4%E7%82%B9%E5%BA%9F%E8%AF%9D" tabindex="-1">先说点废话</h3><p>最近在实际业务中，需要编写一个方法根据数组中每一个对象的一个相同字段，来将该字段值相等的对象重新编入一个数组，返回一个嵌套的数组对象，特地来做个总结。<br />当然需要注意的是，在开发过程这种数组的处理函数，应当被编写到项目的公共工具函数库中全局调用</p><h4 id="%E7%9B%AE%E6%A0%87%E5%AF%B9%E8%B1%A1%E6%95%B0%E7%BB%84" tabindex="-1">目标对象数组</h4><pre><code class="language-">let dataArr = [{id: 1,anyId: 1023,anyVal: &#39;fx67ll&#39;,value: &#39;value-1&#39;},{id: 2,anyId: 1024,anyVal: &#39;fx67ll&#39;,value: &#39;value-2&#39;},{id: 3,anyId: 10086,anyVal: &#39;ll&#39;,value: &#39;value-3&#39;},{id: 1,anyId: 10086,anyVal: &#39;fx67&#39;,value: &#39;value-4&#39;},{id: 2,anyId: 1024,anyVal: &#39;ll&#39;,value: &#39;value-5&#39;},];</code></pre><h4 id="%E5%87%86%E6%8D%A2%E5%90%8E%E7%9A%84%E5%AF%B9%E8%B1%A1%E6%95%B0%E7%BB%84" tabindex="-1">准换后的对象数组</h4><pre><code class="language-">[{&quot;key&quot;: 1,&quot;data&quot;: [{&quot;id&quot;: 1,&quot;anyId&quot;: 1023,&quot;anyVal&quot;: &quot;fx67ll&quot;,&quot;value&quot;: &quot;value-1&quot;}, {&quot;id&quot;: 1,&quot;anyId&quot;: 10086,&quot;anyVal&quot;: &quot;fx67&quot;,&quot;value&quot;: &quot;value-4&quot;}]}, {&quot;key&quot;: 2,&quot;data&quot;: [{&quot;id&quot;: 2,&quot;anyId&quot;: 1024,&quot;anyVal&quot;: &quot;fx67ll&quot;,&quot;value&quot;: &quot;value-2&quot;}, {&quot;id&quot;: 2,&quot;anyId&quot;: 1024,&quot;anyVal&quot;: &quot;ll&quot;,&quot;value&quot;: &quot;value-5&quot;}]}, {&quot;key&quot;: 3,&quot;data&quot;: [{&quot;id&quot;: 3,&quot;anyId&quot;: 10086,&quot;anyVal&quot;: &quot;ll&quot;,&quot;value&quot;: &quot;value-3&quot;}]}]</code></pre><h3 id="%E7%BC%96%E5%86%99%E5%87%BD%E6%95%B0%E7%9A%84%E6%80%9D%E8%B7%AF" tabindex="-1">编写函数的思路</h3><ol><li>首先肯定是一个循环，因为需要循环来比对数组中每个对象相同的字段的值</li><li>其次，根据比对的字段值判断是否存在重复，如果重复存在新的数组中，不重复则添加到之前定义过的数组中，完成分组</li><li>最后，返回处理完成的对象数组</li></ol><h4 id="%E6%96%B9%E6%B3%95%E4%B8%80" tabindex="-1">方法一</h4><pre><code class="language-">// arr 目标对象数组// filed 分组字段function classifyArrayGroupBySameFieldAlpha(arr, filed) {let temObj = {}for (let i = 0; i &lt; arr.length; i++) {let item = arr[i]if (!temObj[item[filed]]) {temObj[item[filed]] = [item]} else {temObj[item[filed]].push(item)}}let resArr = []Object.keys(temObj).forEach(key =&gt; {resArr.push({key: key,data: temObj[key],})})return resArr}</code></pre><h4 id="%E6%96%B9%E6%B3%95%E4%BA%8C" tabindex="-1">方法二</h4><pre><code class="language-">// arr 目标对象数组// filed 分组字段function classifyArrayGroupBySameFieldVBeta(arr, filed) {let temObj = {};let resArr = [];for (let i = 0; i &lt; arr.length; i++) {if (!temObj[arr[i][filed]]) {resArr.push({key: arr[i][filed],data: [arr[i]]});temObj[arr[i][filed]] = arr[i]} else {for (let j = 0; j &lt; resArr.length; j++) {if (arr[i][filed] === resArr[j].key) {resArr[j].data.push(arr[i]);break}}}}return resArr}</code></pre><h3 id="%E6%8B%93%E5%B1%95%E2%80%94%E2%80%94%E2%80%94%E2%80%94es6%E7%9A%84%E6%96%B0%E6%96%B9%E6%B3%95object.keys" tabindex="-1">拓展————ES6的新方法<code>Object.keys</code></h3><ol><li><code>Object.keys()</code>方法用于返回一个由一个给定对象的自身可枚举属性组成的数组，数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致</li><li>需要注意的传不同类型的变量，返回的数组值也不同<ul><li>如果传入对象，则返回属性名数组</li><li>如果传入字符串，则返回索引</li><li>如果数组，则返回索引</li><li>如果构造函数，则返回空数组或者属性名</li></ul></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJs/blob/master/js-blog/2022/2022-11/handle-array_group-by-same-field.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[解决使用`npm install`或`npm i`命令之后报`Unexpected token in JSON at position`错误的问题  ]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/npm-install-error" />
                <id>tag:https://fx67ll.xyz,2022-10-21:npm-install-error</id>
                <published>2022-10-21T17:29:56+08:00</published>
                <updated>2025-07-02T11:26:10+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E7%BD%91%E4%B8%8A%E5%A4%A7%E5%A4%9A%E6%95%B0%E7%9A%84%E6%95%99%E7%A8%8B%E9%83%BD%E6%98%AF%E4%BB%A5%E4%B8%8B%E5%87%A0%E4%B8%AA%E6%AD%A5%E9%AA%A4%E6%8C%A8%E4%B8%AA%E8%AF%95%E4%B8%80%E9%81%8D%EF%BC%8C%E5%8C%85%E6%8B%AC-stackoverflow-%E4%B8%8A%E4%B9%9F%E6%98%AF%E8%BF%99%E4%B9%88%E8%AF%B4%E7%9A%84" tabindex="-1">网上大多数的教程都是以下几个步骤挨个试一遍，包括 <a href="https://stackoverflow.com/questions/51358648/npm-install-error-unexpected-token-in-json-at-position-0" target="_blank">stackoverflow</a> 上也是这么说的</h3><ol><li>删除<code>node_modules</code>文件夹</li><li>删除<code>package-lock.json</code>文件</li><li>强制清除npm缓存 <code>npm cache verify</code>，如果还不行就使用强力清除 <code>npm cache clean --force</code></li><li>修改npm源地址为官方源或者淘宝源 <em>（坑就在这里！先别往下看，思考一下为什么这里有坑？）</em></li></ol><pre><code class="language-">&#96;npm config set registry http://www.npmjs.org/&#96;  &#96;npm config set registry http://registry.npm.taobao.org&#96;  </code></pre><ol start="5"><li>上述四步都完成了之后，执行<code>npm install</code>或<code>npm i</code>命令理论上（当然需要排除网络问题或者node自身的问题）来说就没有问题了，但是我实践了之后缺始终不能解决，确实给我带来了很大的困扰</li></ol><h3 id="%E4%B8%8A%E9%9D%A2%E7%9A%84%E7%AC%AC%E5%9B%9B%E6%AD%A5%E4%B8%BA%E4%BB%80%E4%B9%88%E6%9C%89%E9%97%AE%E9%A2%98" tabindex="-1">上面的第四步为什么有问题</h3><p>新的npm地址已经改为 <em><a href="https://registry.npmjs.org/" target="_blank">https://registry.npmjs.org/</a></em>，网上大多数教程还写着 <s><a href="https://www.npmjs.org/" target="_blank">https://www.npmjs.org/</a></s>，所以如果你走了上面的第四步，并选择使用官方源地址，那么，你会百思不得其解，究竟是为什么还在报错？甚至怀疑人生！！！</p><h3 id="%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%E5%B8%A6%E6%9D%A5%E7%9A%84%E6%80%9D%E8%80%83" tabindex="-1">这个问题带来的思考</h3><p>通过网上的博文来找到解决问题的思路没问题，但是一定要带着怀疑的态度思考，确定是否合理。在这个问题中，我下意识的认为博文中的官方地址一定是对的，一定是我的问题，所以才会百思不得其解，究竟是哪里出了问题，大概这就是灯下黑吧。在今后处理问题的过程中，一定要保持怀疑的态度思考</p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llNode/blob/main/node-blog/npm-install-error.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[使用nvm安装以及管理多版本node教程]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/nvm-use" />
                <id>tag:https://fx67ll.xyz,2022-10-21:nvm-use</id>
                <published>2022-10-21T17:28:12+08:00</published>
                <updated>2025-07-02T11:34:20+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E5%AE%89%E8%A3%85nvm%E3%80%81node%E3%80%81npm" tabindex="-1">安装nvm、node、npm</h3><ol><li>下载nvm安装包，推荐使用1.1.7，我个人使用1.1.8会有中文乱码的报错</li><li>点击exe文件，注意修改nvm的安装根目录以及node的安装根目录，后者是以后管理多版本node的源文件储存地址</li><li>打开系统cmd，依次输入一下目录<ul><li><code>nvm -version</code> 检查nvm是否安装成功</li><li><code>nvm install (你需要安装的node版本号)</code> 可以去官网查看 <a href="https://nodejs.org/zh-cn/download/releases/" target="_blank">以往版本列表</a></li><li>注意！！！上一步会直接同时安装相对应版本npm</li><li><code>nvm use (你需要安装的node版本号)</code> 一般还会弹出系统权限使用框，一定要点通过</li><li><code>node -v</code> 检查node是否安装成功</li><li><code>npm -v</code> 检查npm是否安装成功</li></ul></li></ol><h4 id="%E4%B8%A4%E4%B8%AA%E7%89%88%E6%9C%AC%E5%8F%B7%E9%83%BD%E5%8F%AF%E4%BB%A5%E5%87%BA%E6%9D%A5%E4%BA%86%EF%BC%8C%E8%AF%B4%E6%98%8Envm%E7%AE%A1%E7%90%86node%E7%89%88%E6%9C%AC%E5%B0%B1%E5%B7%B2%E7%BB%8F%E6%88%90%E5%8A%9F%E4%BA%86" tabindex="-1">两个版本号都可以出来了，说明nvm管理node版本就已经成功了</h4><h3 id="%E4%BD%BF%E7%94%A8nvm%E7%AE%A1%E7%90%86node%E7%89%88%E6%9C%AC" tabindex="-1">使用nvm管理node版本</h3><ol><li>一般需要安装什么版本安装多少node都可以，可以通过 <a href="https://nodejs.org/zh-cn/download/releases/" target="_blank">官网来查看所有node的历史版本</a></li><li>使用<code>nvm list</code>，可查看当前所有已安装的node版本，版本号前带<code>*</code>的表示当前正在使用的node版本</li><li>如果需要删除什么版本的ndoe，可以使用命令<code>nvm uninstall (你需要删除的node版本号)*</code>即可</li></ol><h3 id="%E4%BD%BF%E7%94%A8shell%E5%91%BD%E4%BB%A4%E5%88%87%E6%8D%A2npm%E6%BA%90" tabindex="-1">使用shell命令切换npm源</h3><ul><li>设置官方npm源</li></ul><pre><code class="language-">npm config set registry http://registry.npmjs.org/</code></pre><ul><li>查看当前使用的npm源地址</li></ul><pre><code class="language-">npm get registry</code></pre><h3 id="%E4%BD%BF%E7%94%A8nrm%E6%9F%A5%E7%9C%8B%E5%92%8C%E5%88%87%E6%8D%A2%E9%95%9C%E5%83%8F" tabindex="-1">使用nrm查看和切换镜像</h3><ol><li>使用npm全局安装 <code>npm install -g nrm</code></li><li>查看源列表 <code>nrm ls</code></li></ol><pre><code class="language-">npm ---------- https://registry.npmjs.org/yarn --------- https://registry.yarnpkg.com/tencent ------ https://mirrors.cloud.tencent.com/npm/cnpm --------- https://r.cnpmjs.org/taobao ------- https://registry.npmmirror.com/npmMirror ---- https://skimdb.npmjs.com/registry/</code></pre><ol start="3"><li>切换源列表 <code>nrm use (你需要使用的源名称)</code></li></ol><h2 id="%E9%9C%80%E8%A6%81%E6%B3%A8%E6%84%8F%E6%96%B0%E7%9A%84npm%E5%9C%B0%E5%9D%80%E5%B7%B2%E7%BB%8F%E6%94%B9%E4%B8%BA-https%3A%2F%2Fregistry.npmjs.org%2F%EF%BC%8C%E7%BD%91%E4%B8%8A%E5%A4%A7%E5%A4%9A%E6%95%B0%E6%95%99%E7%A8%8B%E8%BF%98%E5%86%99%E7%9D%80-https%3A%2F%2Fwww.npmjs.org%2F%EF%BC%8C%E5%88%87%E5%8B%BF%E5%85%A5%E5%9D%91%EF%BC%81%EF%BC%81%EF%BC%81%E5%88%87%E8%AE%B0%EF%BC%81%EF%BC%81%EF%BC%81%E5%88%87%E8%AE%B0%EF%BC%81%EF%BC%81%EF%BC%81%E5%88%87%E8%AE%B0%EF%BC%81%EF%BC%81%EF%BC%81" tabindex="-1">需要注意新的npm地址已经改为 <em><a href="https://registry.npmjs.org/" target="_blank">https://registry.npmjs.org/</a></em>，网上大多数教程还写着 <s><a href="https://www.npmjs.org/" target="_blank">https://www.npmjs.org/</a></s>，切勿入坑！！！切记！！！切记！！！切记！！！</h2><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llNode/blob/main/node-blog/install-nvm.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Dva.js 快速上手指南]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/dva-quick-start" />
                <id>tag:https://fx67ll.xyz,2022-10-20:dva-quick-start</id>
                <published>2022-10-20T20:10:50+08:00</published>
                <updated>2025-07-02T11:26:18+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E5%85%88%E8%AF%B4%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说些废话</h3><p>最近在开发React技术栈的项目产品，对于数据状态的管理使用了<code>Dva.js</code>，作为一个资深的<a href="https://ow.blizzard.cn/home" target="_blank">ow</a>玩家，我看到这个名字第一反应就是————这不是<a href="https://ow.blizzard.cn/home" target="_blank">ow</a>里的一个女英雄吗?仔细阅读了官方文档之后，发现开发者还真是因为这个角色获得灵感，来命名这个数据状态管理插件，果然开发大佬都是工作和休闲两不误~</p><p>学过React的同学都知道它的技术栈非常多且杂，所以每当你使用React的时候都需要引入很多的模块，那么Dva就是把这些用到的模块集成在一起，比如一些需要引入的依赖<code>react-saga</code>/<code>react-loger</code>、必写的<code>ReactDOM.render</code>、<code>provider、connect</code>包裹等都省去不写，形成一定的架构规范，大大提高我们的开发效率</p><p>今天，就来写一份文档，帮助后续使用Dva的开发者更好得在实际项目中*（PS:需要是以UMI为基础框架，纯Dva来构建项目可以直接看文章结尾的参考文档列表）*上手使用</p><h2 id="%E4%BB%80%E4%B9%88%E6%98%AFdva" tabindex="-1">什么是Dva</h2><p><code>Dva</code>首先是一个基于<code>redux</code>和<code>redux-saga</code>的数据流方案，然后为了简化开发体验，<code>Dva</code>还额外内置了<code>react-router</code>和<code>fetch</code>，所以也可以理解为一个轻量级的应用框架。</p><p>在我目前的项目中，更多是使用数据状态管理的功能，他在我司的fish框架中做了内嵌，在主流的React开发框架UMI中也做了内嵌适配，使用起来非常方便快速。</p><p><code>Dva</code>设计的目的就是简化元素，降低难度，让你不用管他怎么实现的，我们按照默认的这个规则去写就可以</p><h3 id="%E6%95%B0%E6%8D%AE%E6%B5%81%E5%90%91" tabindex="-1">数据流向</h3><p>数据的改变发生通常是通过用户交互行为或者浏览器行为（如路由跳转等）触发的，当此类行为会改变数据的时候可以通过<code>dispatch</code>发起一个<code>action</code>，如果是同步行为会直接通过<code>reducers</code>改变<code>states</code>，如果是异步行为（副作用）会先触发<code>effects</code>然后流向<code>reducers</code>最终改变<code>states</code></p><h3 id="%E5%88%86%E5%B1%82%E5%BC%80%E5%8F%91" tabindex="-1">分层开发</h3><p>无论是Vue还是React开发，实际的大型应用一定有严格的分层开发规范，确保后续开发的可维护性，主要的分层结构有以下几点：</p><ul><li>Page 负责与用户直接打交道：渲染页面，接受用户的操作输入，侧重于展示型交互性逻辑，这里需要<a href="#jumpNoState">了解无状态组件</a></li><li>Model 负责处理业务逻辑，为 Page 做数据、状态的读写、变换、暂存等，<code>Dva</code>中<code>model</code>就是做了这一层的操作</li><li>Service 负责与 HTTP 接口对接，进行纯粹的数据读写</li></ul><h3 id="%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5" tabindex="-1">基础概念</h3><ol><li>namespace<ul><li><code>model</code>的命名空间，同时也是他在全局<code>state</code>上的属性</li><li>只能用字符串，不支持通过<code>.</code>的方式创建多层命名空间，相当于这个<code>model</code>的<code>key</code></li><li>在组件里面，通过<code>connect</code>这个<code>key</code>将想要引入的<code>model</code>加入</li></ul><pre><code class="language-">import { connect } from &#39;dva&#39;;export default connect(({ namespaceValue }) =&gt; ({ ...namespaceValue }))(DvaCompoent);</code></pre></li><li>state<ul><li>表示<code>model</code>的状态数据</li><li>操作的时候每次都要当作不可变数据<code>immutable data</code>来对待，保证每次都是全新对象，没有引用关系</li></ul></li><li>reducer<ul><li>必须是纯函数，有固定输入输出，主要目的是修改自身<code>state</code></li><li>接受两个参数：之前已经累积运算的结果和当前要被累积的值，返回的是一个新的累积结果，该函数把一个集合归并成一个单值</li><li>需要注意的是同样的输入必然得到同样的输出，它们不应该产生任何副作用<code>effect</code>。并且，每一次的计算都应该使用<code>immutable data</code></li></ul></li><li>effect<ul><li>主要用于异步请求，接口调用之类的</li><li><code>effect</code>被称为副作用，在我们的应用中，最常见的就是异步操作</li><li>它来自于函数编程的概念，之所以叫副作用是因为它使得我们的函数变得不纯，同样的输入不一定获得同样的输出</li></ul></li><li>subscription<ul><li><code>subscription</code>语义是订阅，用于订阅一个数据源，然后根据条件<code>dispatch</code>需要的<code>action</code></li><li>数据源可以是当前的时间、服务器的websocket连接、keyboard输入、geolocation变化、history路由变化等等</li><li>内部定义的函数都会被被执行，执行之后作为监听来处理事务</li></ul></li><li>dispatch<ul><li><code>dispatch</code>是一个用于触发<code>action</code>的函数，<code>action</code>是改变<code>state</code>的唯一途径，但是它只描述了一个行为，而<code>dipatch</code>可以看作是触发这个行为的方式，<code>reducer</code>则是描述如何改变数据的</li><li>在<code>Dva</code>中，<code>connect model</code>的组件通过<code>props</code>可以访问到<code>dispatch</code>，可以调用<code>model</code>中的<code>reducer</code>或者<code>effects</code></li></ul><pre><code class="language-">import { connect } from &#39;dva&#39;;const testCom = props =&gt; {  const { dispatch } = props;  const changeValue = (id, val) =&gt; {  // 调用reducer，一般是同步修改state中的值      dispatch({        type: &#39;dva/save&#39;,        payload: {          param: val        },      });  // 调用effect，一般是发送后台请求  dispatch({    type: &#39;dva/queryValue&#39;,    payload: {      id: id    },  });    };  return(    &lt;div&gt;&#39;hello world&#39;&lt;/div&gt;  )}export default connect(({ dva }) =&gt; ({ ...dva }))(testCom);</code></pre></li></ol><h4 id="model%E4%B8%AD%E7%9A%84effects%E5%87%BD%E6%95%B0%E8%A7%A3%E6%9E%90" tabindex="-1">Model中的Effects函数解析</h4><p>需要注意的是：<code>Effects</code>里面的函数都是<a href="https://blog.csdn.net/weixin_46174785/article/details/108419048" target="_blank">Generator函数</a></p><ol><li>yield<ul><li>固定关键词，<code>Generator</code>函数自带的关键词，和<code>*</code>搭配使用，有点像<code>async</code>和<code>await</code>，使用<code>*</code>则表明它是<code>Generator</code>函数</li><li>然后每使用一个<code>yield</code>就是告诉程序这里是异步，需要等待这个后面的代码执行完成，同步代码可不使用该关键词</li></ul></li><li>payload<ul><li>页面上通过<code>dispatch</code>传过来的<code>payload</code>同名参数</li></ul></li><li>select<ul><li><code>Dva</code>中<code>Effects</code>函数的固定传参</li><li>用于拿到<code>model</code>中<code>state</code>的数据，需要注意的是，<code>state</code>后面跟命名空间<code>namespace</code>的值</li></ul><pre><code class="language-">const data = yield select((state) =&gt; state.namespaceName.valueName);</code></pre></li><li>call<ul><li><code>Dva</code>中<code>Effects</code>函数的固定传参</li><li>第一个参数是一个异步函数，<code>payload</code>是参数，可以通过<code>call</code>来执行一个完整的异步请求，又因为<code>yield</code>的存在，就实现了异步转同步的方案</li></ul><pre><code class="language-">const { data } = yield call(queryInterface, payload);</code></pre></li><li>put<ul><li><code>Dva</code>中<code>Effects</code>函数的固定传参</li><li>可以使用同<code>model</code>中的<code>Reducers</code>或者<code>Effects</code>，通过<code>Reducers</code>来实现数据到页面的更新，也可以通过<code>put</code>实现<code>Effects</code>的嵌套使用</li></ul><pre><code class="language-">yield put({type: &#39;save&#39;,payload: {  ...payload},});</code></pre></li></ol><h3 id="%E5%BC%80%E5%8F%91%E7%9B%AE%E5%BD%95" tabindex="-1">开发目录</h3><p>由于公司的fish框架以及常见的umi框架都对Dva做了深度继承，会默认将<code>src/models</code>下的<code>model</code>定义自动挂载，只需要在<code>model</code>文件夹中新建文件即可新增一个<code>model</code>用来管理组件状态，对于某个<code>page</code>文件夹下面的<code>model</code>也会默认挂载</p><pre><code class="language-">├─assets &#96;静态资源&#96;├─components &#96;公共组件&#96;├─config &#96;路由和环境配置&#96;├─constants &#96;全局静态常量&#96;├─locale &#96;国际化&#96;│  ├─en_US &#96;英文配置&#96;│  └─zh_CN &#96;中文配置&#96;├─models &#96;全局数据状态&#96; *Dva涉及的目录*├─pages &#96;页面目录，用我参与开发的其中一个目录来作为示例&#96; *Dva涉及的目录*│  ├─NodeConfig  &#96;NodeConfig示例目录&#96;│  │  ├─components│  │  │  ├─Select &#96;Select组件页面文件&#96; *Dva涉及的目录*│  │  │  │  └─components│  │  │  │      ├─AudienceInfo│  │  │  ││├─index.js│  │  │  ││└─index.less│  │  │  │      ├─BlackList│  │  │  ││├─index.js│  │  │  ││└─index.less│  │  │  │      ├─ControlGroup│  │  │  ││├─index.js│  │  │  ││└─index.less│  │  │  │      └─GroupSelect│  │  │  ││├─index.js│  │  │  ││└─index.less│  │  │  │├─index.js│  │  │  │└─index.less│  │  ├─models│  │  │  ├─select.js &#96;Select组件数据状态管理&#96; *Dva涉及的目录*│  │  └─services├─services &#96;全局接口配置&#96;├─themes &#96;全局样式主题&#96;└─utils &#96;js通用工具&#96;PS: 该树形图通过 &#96;windows shell&#96; 自带的 &#96;tree&#96; 命令生成</code></pre><h2 id="%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8dva" tabindex="-1">如何使用Dva</h2><h3 id="%E9%A6%96%E5%85%88%E5%AE%9A%E4%B9%89%E4%B8%80%E4%B8%AA%E7%AE%80%E6%98%93%E7%9A%84model%E7%A4%BA%E4%BE%8B" tabindex="-1">首先定义一个简易的model示例</h3><pre><code class="language-">export default {  namespace: &#39;dva&#39;,  state: {id: &#39;&#39;,    value: {},  },  effects: {// 所有effect前必须要加 *    *queryValue({ payload }, { select, call, put }) {  const params = {  id: payload.id ? payload.id : yield select(state =&gt; state.select.id)  }      const { data } = yield call(queryInterface, params); // queryInterface是定义好的后台请求接口，一般用axios或fetch来完成      yield put({ type: &#39;save&#39;, payload: data });    },  },  reducers: {    save(state, { payload }) {      return {        ...state,        ...payload,      };    },  },  subscriptions: {    keyboardWatcher({ dispatch }) {  key(&#39;⌘+up, ctrl+up&#39;, () =&gt; { dispatch( {type:&#39;save&#39;}) });    },  },};</code></pre><h3 id="%E7%84%B6%E5%90%8E%E6%8A%8Amodel%E5%92%8C%E7%BB%84%E4%BB%B6%E7%BB%91%E5%AE%9A%E5%9C%A8%E4%B8%80%E8%B5%B7" tabindex="-1">然后把model和组件绑定在一起</h3><p>React的Connect函数是一种<a href="#jumpCurry">柯里化写法</a></p><pre><code class="language-">import { connect } from &#39;dva&#39;;const testCom = props =&gt; {  const { helloWorld = &#39;hello world&#39;} = props;  return(    &lt;div&gt;{ helloWorld }&lt;/div&gt;  )}// 绑定之后就可以在testCom组件中使用命名为dva的model了export default connect(({ dva }) =&gt; ({ ...dva }))(testCom);</code></pre><p><span id="jumpCurry"></span></p><h4 id="%E6%9F%AF%E9%87%8C%E5%8C%96" tabindex="-1">柯里化</h4><p>柯里化是把接受多个参数的函数转换成接受一个单一参数的函数（PS：Scala语言中也有类似的设计）</p><pre><code class="language-">// 柯里化var foo = function(x) {return function(y) {return x + y}}foo(3)(4)// 普通方法var add = function(x, y) {return x + y;}add(3, 4) </code></pre><p><span id="jumpNoState"></span></p><h4 id="%E6%97%A0%E7%8A%B6%E6%80%81%E7%BB%84%E4%BB%B6" tabindex="-1">无状态组件</h4><p>创建无状态组件是为了创建纯展示组件，这种组件只负责根据传入的props来展示，不涉及到要改变state状态的操作，<br />在实际项目中页面组件被写成无状态的组件，通过简单组合可以构建成页面或复杂组件，通过多个简单组件来合并成一个复杂的大应用</p><pre><code class="language-">const NoStateComponent = props =&gt; {  const { helloWorld = &#39;hello world&#39;} = props;  return(    &lt;div&gt;{ helloWorld }&lt;/div&gt;  )}export default NoStateComponent;</code></pre><p><em>无状态组件的优点</em></p><ol><li>由于是无状态组件，所以无状态组件就不会在有组件实例化的过程，无实例化过程也就不需要分配多余的内存，从而性能得到一定的提升</li><li>代码整洁、可读性高，对于大型项目的开发维护非常有好处</li></ol><p><a href="https://dvajs.com/guide/#%E7%89%B9%E6%80%A7" target="_blank">参考文档一 ———— Dva官方文档</a><br /><a href="https://umijs.org/docs/max/dva" target="_blank">参考文档二 ———— UMI官方文档</a><br /><a href="https://dvajs.com/guide/#%E7%89%B9%E6%80%A7" target="_blank">参考文档三 ———— REACT基础笔记 MODEL分层</a><br /><a href="https://blog.csdn.net/harry_yaya/article/details/111408248" target="_blank">参考文档四 ———— 前端数据流方案Dva</a><br /><a href="https://blog.csdn.net/weixin_38398698/article/details/93387757" target="_blank">参考文档五 ———— 浅析dva (史上最全的dva用法及分析)</a><br /><a href="https://www.cnblogs.com/Shyno/p/15689336.html" target="_blank">参考文档六 ———— 【dva】model中effects函数的解析 </a><br /><a href="https://blog.csdn.net/weixin_46174785/article/details/108419048" target="_blank">参考文档七 ———— Generator 函数的详解</a><br /><a href="https://www.cnblogs.com/irobotzz/p/12292283.html" target="_blank">参考文档八 ———— React connect()() 双括号 --柯里化写法</a><br /><a href="https://segmentfault.com/a/1190000018265172" target="_blank">参考文档九 ———— 高级函数技巧-函数柯里化</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llReact/blob/master/react-blog/2022/2022-10/dva.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[ 2022 前端面试总结]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/font-interview-2022" />
                <id>tag:https://fx67ll.xyz,2022-10-17:font-interview-2022</id>
                <published>2022-10-17T17:48:40+08:00</published>
                <updated>2025-08-11T16:45:46+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E5%85%88%E8%AF%B4%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说些废话</h3><p>这是一篇笔者面试记录的文章，并非要写一篇非常全面的面试题汇总文章，希望看完本文对您能有一定的帮助。</p><p>首先，面试对个人来说，就是一种对以往工作之中经验积累的一种检验，如果平时在工作中对自己遇到的问题、难点、新知识点，没有花时间去学习积累，<br />那么我相信，面试的时候花时间背面试题，也不一定能胜任预期的工作，不过摸鱼混工时的工作除外~</p><p>其次，面试中除了技术面试，还有针对项目中运用的技术亮点和个人能够提供的技术解决方案等等能力的展现，这方面的能力没实际解决过就一定无法提出自己的解决方案。<br />所以居安思危，平时就严格要求自己，对于疑难问题、性能问题，不逃避问题，迎难而上去解决，才能使得自己真正在技术岗位上站稳脚跟。</p><h2 id="%E5%B7%A5%E4%BD%9C%E4%B8%AD%E5%BF%85%E5%A4%87%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A1%86%E6%9E%B6%E5%B7%A5%E5%85%B7" tabindex="-1">工作中必备的核心框架工具</h2><p>Vue和React是前端开发岗必备的两种技术栈，以下是工作中项目开发必须要具备的框架技能，参考大纲查漏补缺</p><h3 id="vue%EF%BC%88vue2%2F3%EF%BC%89" tabindex="-1">Vue（Vue2/3）</h3><ol><li>Vue主流组件库（<a href="https://antdv.com/components/overview" target="_blank">ant-design</a>、<a href="https://element.eleme.cn/#/zh-CN/component/installation" target="_blank">element-ui</a>、<a href="https://arco.design/vue/docs/start" target="_blank">arco-design</a>）</li><li>Vue组件封装（公共组件抽离，npm组件发布以及<a href="https://blog.csdn.net/lwplvx/article/details/115367282" target="_blank">私有部署</a>，编写组件必须考虑的几个要素props、methods、样式）</li><li>Vue父子、兄弟组件通信（Vuex，<a href="https://blog.csdn.net/qq_26834399/article/details/106387585" target="_blank">EventBus</a>）</li><li><a href="https://blog.csdn.net/weixin_45791692/article/details/124045505" target="_blank">生命周期函数</a>、<a href="https://blog.csdn.net/weixin_45773421/article/details/106282254" target="_blank">Watch函数</a>、以及<a href="https://blog.csdn.net/alical/article/details/122892108" target="_blank">计算属性Computed</a>的应用</li><li>Vue动态路由，权限相关（菜单页面、按钮操作），单点登录，使用图片验证防止CSFR攻击</li><li>Vue3更新的内容，老旧Vue2项目维护和新项目Vue3技术选型的注意点，Vue配合TypeScript</li></ol><h3 id="react%EF%BC%88react16%E5%8F%8A%E4%BB%A5%E4%B8%8A%EF%BC%89" tabindex="-1">React（React16及以上）</h3><ol><li>企业级开发框架<a href="https://umijs.org/docs/tutorials/getting-started" target="_blank">UMI</a>（简化项目构建和初始开发流程）</li><li>React主流组件库（<a href="https://ant.design/components/overview-cn/" target="_blank">ant-design</a>、<a href="https://arco.design/react/docs/start" target="_blank">arco-design</a>）</li><li>React组件封装（类比Vue）</li><li>React组件通信（<a href="https://cn.redux.js.org/tutorials/essentials/part-1-overview-concepts/" target="_blank">Redux</a>、Redux-thunk、Redux-saga、Redux-logger、<a href="https://dvajs.com/" target="_blank">dva</a>）</li><li><a href="https://zhuanlan.zhihu.com/p/392532496" target="_blank">生命周期函数</a>、<a href="https://blog.csdn.net/shadowfall/article/details/115387015" target="_blank">React Hooks钩子函数</a>的应用</li><li><a href="https://blog.csdn.net/XiaoSen207/article/details/125292167" target="_blank">无状态组件</a>、<a href="https://www.freesion.com/article/77531504180/" target="_blank">项目分层</a>（view + model + service）</li><li>React路由和权限（类比Vue）</li></ol><h3 id="%E5%85%AC%E5%85%B1%E9%83%A8%E5%88%86" tabindex="-1">公共部分</h3><ol><li>webpack<a href="https://zhuanlan.zhihu.com/p/463523330" target="_blank">打包配置</a>、<a href="https://zhuanlan.zhihu.com/p/490076369" target="_blank">性能优化</a>、<a href="http://www.cncsto.com/article/52406" target="_blank">webpack.dev.conf.js添加cdn配置</a></li><li>如何首屏加载优化、图标和图片资源优化</li></ol><h2 id="%E7%9B%AE%E5%89%8D%E9%9D%A2%E8%AF%95%E4%B8%AD%E5%B7%B2%E7%BB%8F%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98" tabindex="-1">目前面试中已经遇到的问题</h2><h3 id="javascript%E5%92%8Ccss" tabindex="-1">JavaScript和CSS</h3><ol><li>ES6的数据类型有哪些？（基本数据类型和引用数据类型，ES6新增<code>Symbol</code>，用于对象属性中唯一的key；新增<code>BigInt</code>用于解决数据量过大Int报错的问题）</li><li>ES6新增的方法有哪些？（<code>new Set()</code>处理数组去重，<code>new Map()</code>链式处理数组，可以搭配Filter使用，再说一些和<code>ForEach</code>的区别，ForEach是在原数组中操作。<br />可以再说说<code>is()</code>，判断值是否相等；<code>assign()</code>，浅拷贝对象；entries()与后者反之和fromEntries()键值对数组转为对象）<ul><li><a href="https://blog.csdn.net/qq_27518937/article/details/118523830" target="_blank">Javascript中的assign()方法到底是浅拷贝还是深拷贝？</a></li><li><a href="https://blog.csdn.net/weixin_65308648/article/details/126323181" target="_blank">Es6新增的对象方法</a></li></ul></li><li>如何判断一个数据是否是对象？（1.isArray 2.Object.prototype.toString().call()，返回’[object Array]'即为 3.实例的构造函数属性constructor指向构造函数，数组.contructor===Array即可 4.使用instanceof亦可，a instanceof Array）<ul><li><a href="https://www.cnblogs.com/echolun/p/10287616.html" target="_blank">JS判断是否是数组的四种做法</a></li></ul></li><li>什么是BFC盒子模型，他的作用是什么（具有 BFC 特性的元素可以看作是隔离了的独立容器，容器里面的元素不会在布局上影响到外面的元素；<br />触发条件：1.overflow 除了 visible 以外的值 (hidden、auto、scroll)。2.绝对定位元素：position (absolute、fixed)。3.display 为 inline-block、table-cells、flex。）<ul><li><a href="https://blog.csdn.net/weixin_39703655/article/details/110403734" target="_blank">盒子模型与BFC</a></li></ul></li><li>null和undefined的区别（null和undefined非严格相等，自身也全等，但是相互不严格相等；null是人为赋予初始空值，undefined是全局对象的属性；typeof(null)不是null，而是object，<br />是因为typeof判断二进制码，object前三位是0，null刚好也是，typeof(undefined)正常；）</li><li>var和let的区别（var是函数作用域，let是块状作用域，let声明的变量不能重复；const声明的变量不可修改；var声明的变量属于window，可以window.访问，let和const的不行）<ul><li><a href="https://blog.csdn.net/m0_63574519/article/details/125257156" target="_blank">var和let的区别</a></li><li><a href="https://blog.csdn.net/qq_44810886/article/details/125141857?spm=1001.2014.3001.5502" target="_blank">const 声明了数组，还能 push 元素吗，为什么？</a></li></ul></li><li><code>push()</code>、<code>pop()</code>、<code>shift()</code>、<code>unshift()</code>、<code>splice()</code>、<code>sort()</code>、<code>reverse()</code>操作的区别。shift删除第一个元素并返回，unshift反之。<br />splice用于添加或删除数组中的元素，array.splice(index,howmany,item1,…,itemX)；index必需，规定从何处添加/删除元素；<br />howmany可选，规定应该删除多少元素，必须是数字，但可以是 “0”，如果未规定此参数，则删除从<code>index</code>开始到原数组结尾的所有元素；<br />item1, …, itemX可选，要添加到数组的新元素<ul><li><a href="https://www.runoob.com/jsref/jsref-splice.html" target="_blank">JavaScript splice() 方法 </a></li></ul></li><li>axios有什么竞品是否有了解过（ajax和fetch，fetch是js原生api，需要自己做一些封装；ajxa针对mvc不是现在mvvm，并且基于原生xhr，还需要引入jquery；axios支持promise，支持防止csrf）<ul><li><a href="https://blog.csdn.net/twodogya/article/details/80223508" target="_blank">ajax、axios、fetch之间的详细区别以及优缺点</a></li><li><a href="https://www.jianshu.com/p/5f17eea7f8f4" target="_blank">Ajax、Fetch、Axios</a></li></ul></li><li>js的深拷贝和浅拷贝（1. 常用深拷贝使用JSON.parse+JSON.stringify、2. object.assign一级属性深拷贝二级属性浅拷贝、3. 用foreach方法递归迭代实现深拷贝、4. 使用第三方函数库，比如loadsh的clonedeep或者underscore的deepclone，clone是浅拷贝）<ul><li><a href="https://blog.csdn.net/weixin_36430673/article/details/123323392" target="_blank">js的深拷贝与浅拷贝</a></li></ul></li><li>H5移动端如何做性能优化（1. 资源压缩，图片压缩，引入资源包压缩，有条件的使用cdn加速，icon雪碧图精灵图。2. 优化dom结构，减少页面的重绘和重排布，首屏支出的页面使用内联css，使用节流防抖的手段减少修改dom事件触发的频率。<br />3. 使用预先加载骨架图的方式优化用户体验，我们之前有用过page-web-skeleton这个chorme组件快速生成骨架图。4. 尽量使用css内置动画，减少使用<code>requestAnimationFrame</code>，他会触发大量的dom操作，<br />而内置动画会调用硬件加速，体验更优。5. 配合后端同学做接口合并，对于首页显示的接口统一为一个总接口，并针对性优化。6. 最后就是优化主要针对不同项目，通过不同的方式去做优化，针对性的修改）<ul><li><a href="https://zhuanlan.zhihu.com/p/516199187" target="_blank">H5前端性能优化图鉴</a></li></ul></li><li>css样式的优先规则（简而言之，内联样式 &gt; ID 选择器 &gt; 类选择器 = 属性选择器 = 伪类选择器 &gt; 标签选择器 = 伪元素选择器，<code>!important</code>最高）<ul><li><a href="https://blog.csdn.net/XiaoSen207/article/details/123265270" target="_blank">CSS的优先级</a></li><li><a href="https://blog.csdn.net/qq_43000315/article/details/126168264" target="_blank">CSS 样式优先级</a></li></ul></li></ol><h3 id="vue" tabindex="-1">Vue</h3><ol><li>Vue的实现原理，<code>Object.defineProperty()</code>；Vue2 和 Vue3 的实现响应式的区别； Vue如何实现数组的响应式（底层代码主要是编写了一套js相同的方法暴露出来，Vue通过原型拦截的方式重写了数组的7个方法）</li><li>Vue如何封装一个组件（定义参数，定义需要暴露出的方法，参数需要写的属性，最后export出来）</li><li>Vue父子组件和兄弟组件的通信方式（1. 第一种方式借助父组件。2. 第二种方式借助eventbus，在main中全局注册事件bus，通过<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>o</mi><mi>n</mi><mtext>和</mtext></mrow><annotation encoding="application/x-tex">on和</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal">o</span><span class="mord mathnormal">n</span><span class="mord cjk_fallback">和</span></span></span></span>emit来监听和触发。3. 使用Vuex）<ul><li><a href="http://t.zoukankan.com/zhaoyingzhen-p-15244958.html" target="_blank">vue学习之----兄弟组件之间通信方式</a></li></ul></li><li>Vue如何实现v-model（使用object.defineProperty设置getset方法，再给绑定的input输入框的keyup事件添加劫持，从而实现mvvm，即数据驱动页面；接着继续吹mvvm和之前jquery时代的区别；<br />jquery主要是链式函数操作，对dom的操作很多，性能上会有一定落后；mvvm是指model，view，viewmodel，最后一个是同步view和model的对象，model是业务逻辑，view是页面ui）<ul><li><a href="https://blog.csdn.net/weixin_52280029/article/details/124900333" target="_blank">Vue的v-model的原理和实现</a></li><li><a href="https://www.cnblogs.com/zixian/p/mvvm.html" target="_blank">什么是MVVM？</a></li></ul></li><li>当Vue使用history模式的时候，出现404，nginx该如何处理（<code>try_files $uri $uri/ /index.html</code>，表示查找当前请求地址对应的文件是否存在，不存在则查找请求地址对应的目录是否存在，还是不存在则重定向到/index.html页面）<ul><li><a href="https://blog.csdn.net/m0_38037668/article/details/122962248" target="_blank">Nginx：vue路由使用history模式刷新404</a></li></ul></li></ol><h3 id="react%EF%BC%88%E4%B8%AA%E4%BA%BA%E7%AE%80%E5%8E%86%E6%9C%AA%E7%AA%81%E5%87%BAreact%E8%83%BD%E5%8A%9B%EF%BC%8C%E6%89%80%E4%BB%A5%E8%BF%99%E6%96%B9%E9%9D%A2%E9%97%AE%E7%9A%84%E6%AF%94%E8%BE%83%E5%B0%91%EF%BC%89" tabindex="-1">React（个人简历未突出react能力，所以这方面问的比较少）</h3><ol><li>redux如何使用？</li><li>redux中传递组件样式css无法生效怎么办？</li></ol><h2 id="%E6%9D%82%E4%B9%B1%E8%AE%B0%E5%BD%95%E4%B8%80%E4%BA%9B" tabindex="-1">杂乱记录一些</h2><ul><li>前端性能优化：<a href="https://www.cnblogs.com/sunidol/p/11546884.html" target="_blank">H5页面如何解决首屏加载白屏问题</a>、<a href="https://blog.csdn.net/JavaMonsterr/article/details/125017666" target="_blank">如何防止内存泄漏</a></li><li>前端安全基础：<a href="https://blog.csdn.net/m0_55854679/article/details/123028852" target="_blank">XSS攻击</a>、<a href="https://www.jianshu.com/p/1fee1a187b11" target="_blank">XSRF攻击</a></li><li>js继承的几种方式：<a href="https://blog.csdn.net/AI_huihui/article/details/120773032" target="_blank">总结JS继承的六种方式</a>、<a href="https://blog.csdn.net/qq_44746317/article/details/124171314" target="_blank">js几种继承</a></li><li>js如何创建对象：<a href="https://www.jb51.net/article/230503.htm" target="_blank">JS创建对象的四种方式</a></li><li>如何实现一个promise：<a href="https://blog.csdn.net/weixin_44178305/article/details/110723671" target="_blank">javaScript之js模拟封装一个promise</a>、<a href="https://www.cnblogs.com/dasusu/p/12047873.html" target="_blank">模拟实现 Promise（小白版）</a></li><li><a href="https://blog.csdn.net/weixin_43312391/article/details/123213412" target="_blank">高德地图</a>、<a href="https://blog.csdn.net/qq_63140863/article/details/120858694" target="_blank">Cesium</a>、<a href="https://blog.csdn.net/gongxifacai_believe/article/details/113071383" target="_blank">ArcGIS</a>有哪些图层？</li><li>原型和原型链有什么作用，简单描述一下？</li></ul><h2 id="%E7%AC%94%E8%AF%95%E9%9D%A2%E8%AF%95%E9%A2%98%E8%AE%B0%E5%BD%95" tabindex="-1">笔试面试题记录</h2><ol><li>一些奇奇怪怪工作中很少用到的冷门CSS知识点考察题目（比如：长50px，宽50px，border-radius: 30px 50px; 是什么效果？）</li><li>数组去重、数组排序以及组合能力的算法题（牛客网和Leecode刷题处理）</li><li>手写sql（加分项）</li></ol><h2 id="%E5%85%AB%E8%82%A1%E6%96%87%E6%B1%87%E6%80%BB%E6%95%B4%E7%90%86" tabindex="-1">八股文汇总整理</h2><p>八股文总会被问到，还是需要看看，大部分八股文遇到的问题也都可以在工作中遇到，你遇到过自然印象就深刻</p><ol><li><a href="https://blog.csdn.net/m0_65450343/article/details/125238400" target="_blank">前端200道面试题及答案（更新中）</a></li><li><a href="https://blog.csdn.net/weixin_44727080/article/details/123108565" target="_blank">50个最新TypeScript面试题合集 – TypeScript开发教程</a></li><li><a href="https://blog.csdn.net/qq_60976312/article/details/125783396" target="_blank">React面试题最全</a></li><li><a href="https://zhuanlan.zhihu.com/p/472168884" target="_blank">react 面试题整理</a></li><li><a href="https://blog.csdn.net/weixin_46232841/article/details/124505088" target="_blank">耗时一个月，React 知识点万字大总结（超全超基础）</a></li><li><a href="https://blog.csdn.net/qq_41370833/article/details/124783929" target="_blank">webpack 面试题整理</a></li><li><a href="https://blog.csdn.net/weixin_43778426/article/details/125651514" target="_blank">vue面试常问的原理</a></li><li><a href="https://blog.csdn.net/allen_he_123/article/details/119478300" target="_blank">12道vue高频原理面试题,你能答出几道?</a></li><li><a href="https://blog.csdn.net/qq_45659769/article/details/119564784" target="_blank">vue面试题整理(2022-持续更新中…)</a></li><li><a href="https://blog.csdn.net/qq_22182989/article/details/125781704" target="_blank">vue3面试题：2022 最新前端 Vue 3.0 面试题及答案</a></li><li><a href="https://blog.csdn.net/weixin_40599109/article/details/110938941" target="_blank">vue3.0 面试题总结</a></li><li><a href="https://blog.csdn.net/qq_44810886/article/details/125141857" target="_blank">高频 JS面试题 数据类型和变量、闭包、作用域、原型链、继承</a></li><li><a href="https://blog.csdn.net/qq_44810886/article/details/124355440" target="_blank">CSS高频面试题</a></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJs/blob/master/js-blog/2022/2022-10/font-job_release.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[大数据面试汇总 🕹️0.4.2]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/bigdata-interview-list" />
                <id>tag:https://fx67ll.xyz,2022-05-12:bigdata-interview-list</id>
                <published>2022-05-12T15:10:51+08:00</published>
                <updated>2025-07-03T10:43:31+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h4 id="%E5%85%88%E8%AF%B4%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说些废话</h4><p>作为一个全栈开发工作者，曾经对公司专职的大数据开发有着浓厚的兴趣，所以尝试学习大数据开发所需要的各种技术栈。</p><p>本文就是我在学习过程中记录下，所遇到的一些大数据面试的提问，仅供参考。</p><p>当然，因为时间精力有限，并非所有的问题我都去记录了答案，如果您不了解某些问题或者不认可我记录的解答，可以带着问题百度或者问ChatCPT，相信会给您留下更深刻的印象。</p><p>最后，读者可以把本文当作模拟面试的提纲，欢迎各位在评论区交流，大家一起成长，努力变得更强！！！</p><h2 id="%E9%AB%98%E9%A2%91%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%82%B9" tabindex="-1">高频基础知识点</h2><h3 id="%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B" tabindex="-1">执行过程</h3><ol><li>MapReduce 的执行过程，如何进行一个文件的计算，shuffle的过程（不需要手写MapReduce或者用java写MapReduce）<ul><li><a href="https://www.jianshu.com/p/5d4f13f38689" target="_blank">MapReduce执行流程及示例</a></li></ul></li><li>HDFS 的读写流程<ul><li><a href="https://blog.csdn.net/qq_17685725/article/details/123243677" target="_blank">HDFS的读写流程步骤（附图文解析）</a></li></ul></li><li>Yarn 的任务提交流程，如何查看任务日志和报错信息<ul><li><a href="https://blog.51cto.com/u_14048416/2342658" target="_blank">hive的执行流程</a></li></ul></li><li>Hive 的执行过程（Hive SQL 如何解析成MapReduce的）<ul><li><a href="https://blog.csdn.net/weixin_43823423/article/details/117264918" target="_blank">Hive SQL转化为MapReduce的过程</a></li><li><a href="https://blog.51cto.com/u_14048416/2342658" target="_blank">hive的执行流程</a></li><li><a href="https://mp.weixin.qq.com/s/3483ib06DFQfhLFOZAv_jA" target="_blank">万字长文详解HiveSQL执行计划</a></li><li><a href="https://mp.weixin.qq.com/s/7yffuvCr17naOS7GeV8tTQ" target="_blank">Hive SQL底层执行过程详细剖析</a></li><li><a href="https://mp.weixin.qq.com/s/5a8bBEDgxErBfkhLsTS70g" target="_blank">Hive底层原理：explain执行计划详解</a></li></ul></li><li>Spark 任务的执行过程<ul><li><a href="https://www.jianshu.com/p/cdb7c7e81f1a" target="_blank">Spark原理 | 任务执行流程</a></li></ul></li><li>Spark SQL 的底层执行过程<ul><li><a href="https://mp.weixin.qq.com/s/CWdBLhgUrLxlsavTFhA0rA" target="_blank">Spark SQL底层执行流程详解</a></li><li><a href="https://mp.weixin.qq.com/s/qotI36Kx3nOINKHdOEf6nQ" target="_blank">Spark底层执行原理详细解析</a></li></ul></li></ol><h3 id="linux" tabindex="-1">Linux</h3><ol><li>如何查看内存，CPU占用<ul><li><a href="https://blog.csdn.net/weixin_44431371/article/details/131528718" target="_blank">linux下查看cpu使用率和内存占用</a></li></ul></li><li>Shell脚本如何定义函数调用</li><li>Shell脚本第一行：<code>#!/bin/bash</code>的含义<ul><li>第一行的内容指定了shell脚本解释器的路径，而且这个指定路径只能放在文件的第一行</li><li>第一行写错或者不写时，系统会有一个默认的解释器进行解释</li></ul></li><li>Linux脚本授权是什么？Linux授权命令是什么？<ul><li><code>chmod [{ugoa}{+-=}{rwx}] [文件或目录]</code></li><li><code>chmod [mode=421] [文件或目录]</code></li></ul></li><li>Shell标准输出和标准错误是什么？</li><li>工作中常用的Linux命令有哪些？</li></ol><h3 id="mysql" tabindex="-1">MySQL</h3><ol><li>sql的执行计划，执行错误报警机制</li><li>索引有什么优缺点</li><li>什么时候会导致索引失效</li><li>如果我给一张表的所有字段添加索引会有什么样的问题</li><li><em>如何进行sql调优</em> <strong>高频</strong><ul><li><a href="https://fx67ll.xyz/archives/mysql-optimize" target="_blank">MySQL在工作中的优化方案总结</a></li></ul></li></ol><h3 id="hadoop%EF%BC%88hdfs%2Fmapreduce%2Fyarn%EF%BC%89" tabindex="-1">Hadoop（HDFS/MapReduce/Yarn）</h3><ol><li>Haddop3对于Hadoop2有哪些新特性<ul><li><a href="https://mp.weixin.qq.com/s/qCauPHgzRVdDbzZKhz34BA" target="_blank">Hadoop 3.x 版本相对于 Hadoop 2.x的新特性</a></li></ul></li><li>HDFS 常用命令有哪些<ul><li><a href="https://blog.csdn.net/m0_56602092/article/details/126704878" target="_blank">HDFS常用命令</a></li></ul></li><li>工作中遇到 MapReduce 流程的相关问题如何排查</li><li>其他问题汇总<ul><li><a href="https://mp.weixin.qq.com/s/yewLzQ2EtjEuvKN6q_MjYQ" target="_blank">精选Hadoop高频面试题17道</a></li></ul></li></ol><h3 id="hive" tabindex="-1">Hive</h3><ol><li><em>工作中如何进行Hive优化</em> <strong>高频</strong><ul><li><a href="https://fx67ll.xyz/archives/hive-optimize-inwork" target="_blank">Hive在工作中的调优总结</a></li></ul></li><li>Hive中有任务跑的时间比较长，怎么优化</li><li>Hive任务处理过程中遇到过什么样的问题<ul><li><a href="https://mp.weixin.qq.com/s/0YL0skTG9448Os3Md7CIzg" target="_blank">Hive企业级性能优化</a></li></ul></li><li>Hive SQL 查询比较慢（数据倾斜），怎么处理<ul><li><a href="https://mp.weixin.qq.com/s/EzwcPMhqklHK7rMEc-3iyw" target="_blank">实操 | Hive 数据倾斜问题定位排查及解决</a></li><li><a href="https://mp.weixin.qq.com/s/hz_6io_ZybbOlmBQE4KSBQ" target="_blank">Hive千亿级数据倾斜解决方案</a></li></ul></li><li>Hive的参数设置用过哪些，有什么作用</li><li>Hive中表关联方式join的分类、用法、应用场景</li><li>什么时候会走MapJoin<ul><li><a href="https://blog.csdn.net/sinat_37574187/article/details/120444216" target="_blank">mapjoin的使用方法以及注意事项</a></li></ul></li><li>什么情况下会产生Hive小文件，小文件有什么危害</li><li>Hive的查询过程，哪些方法可以提高查询效率 <strong>变相的问优化</strong><ul><li><a href="https://blog.csdn.net/weixin_45695430/article/details/123791209" target="_blank">Hive 提高查询效率的方法</a></li></ul></li><li>Hive的窗口函数，常用窗口函数，怎么使用<ul><li>窗口函数<code>row_number</code>、<code>rank</code>、<code>dense_rank</code>之间的区别<blockquote><p>row_number是行号，不会重复<br />rank数据相同的，给出并列排名，但是会跳跃<br />dense_rank类似于rank，但不会跳跃<br /><a href="https://blog.csdn.net/jianai858/article/details/118490540" target="_blank">hive窗口函数之排名函数row_number、rank和dense_rank</a><br /><a href="https://mp.weixin.qq.com/s/ByAKgzFK_DvyrL7-jr7wVw" target="_blank">Hive窗口函数保姆级教程</a><br /><a href="https://mp.weixin.qq.com/s/8LC2yjjGo8J81fZJt6CxkQ" target="_blank">Hive窗口函数/分析函数详解</a></p></blockquote></li><li>如何用窗口函数去重</li></ul></li><li>Hive SQL的数据去重方式<ul><li>distinct、groupby、row_number</li><li><a href="https://wenku.baidu.com/view/c2be4d6b757f5acfa1c7aa00b52acfc789eb9f9f.html" target="_blank">hive中三种去重的方法</a></li></ul></li><li>项目中Hive有多少表</li><li>Hive查询组件Impala了解吗？简单介绍下<ul><li><a href="https://blog.csdn.net/qq_24852439/article/details/121686496" target="_blank">Impala入门操作</a></li></ul></li><li>Hive中的行列转换，除了case when，还有别的方法吗<ul><li>行转列：<code>collect_set()</code>、<code>collect_list()</code>、<code>concat_ws()</code></li><li>列转行：<code>explode()</code>、<code>split()</code>、<code>LATERAL VIEW</code></li><li><a href="https://blog.csdn.net/weixin_42679482/article/details/120422007" target="_blank">Hive sql 行列转换(行转列，列转行)</a></li></ul></li><li>Hive中内部表和外部表的区别？内部表和外部表如果删除了元数据是由谁来维护？元数据的存储位置和管理者是谁<ul><li>从创建表和删除表两个方面说明</li><li>metastore，master节点上</li><li>mysql，metastore</li></ul></li><li>Hive分区和分桶的区别？分桶的原理？分区可以提高查询效率吗？分区越多越好吗<ul><li>MR中：按照key的hash值去模除以reductTask的个数</li><li>Hive中：按照分桶字段的hash值去模除以分桶的个数</li><li>缩小数据查询范围，提高查询效率，但是不是分区越多越好</li><li>Hive中如果有过多的分区，由于底层是存储在HDFS上，HDFS上只用于存储大文件而非小文件，因为过多的分区会增加NameNode的负担</li><li>Hive SQL会转化为MapReduce， MapReduce会转化为多个task，过多小文件的话，每个文件一个task，每个taskー个JVM实例，JVM的开启与销毀会降低系统效率</li><li>合理的分区不应该有过多的分区和文件目录，并且每个目录下的文件应该足够大</li><li><a href="https://blog.csdn.net/Shockang/article/details/118074010" target="_blank">Hive中分区是否越多越好？</a></li></ul></li><li>如何反查哪条Hive SQL执行较慢？<ul><li><a href="https://blog.csdn.net/weixin_45500089/article/details/120022345" target="_blank">【Hive】从执行计划DAG中执行慢的Task，找到对应SQL逻辑片段</a></li><li><a href="https://mp.weixin.qq.com/s/Xz31A1rje7vYwGcYzHXfcw" target="_blank">一文学完所有的Hive Sql</a></li></ul></li><li>Hive查询在工作中遇到什么样的难点？如何解决？</li></ol><h3 id="hbase" tabindex="-1">Hbase</h3><ol><li>Hbase查询用的多吗，有没有做过优化？<ul><li><a href="https://mp.weixin.qq.com/s/1orGDCHQuDmLTenk4Jq4Jw" target="_blank">万字长文详解HBase读写性能优化</a></li><li><a href="https://zhuanlan.zhihu.com/p/124928432" target="_blank">Hbase快速入门（超精炼总结）</a></li><li>大多数key-value数据库是为了高频写入而设计的，而不是为了高速读取！用来做高性能查询完全是个方向性错误</li></ul></li><li>Hbase查询过滤器用过吗，简单介绍下<ul><li>RowFilter、FamilyFilter、QualifierFilter、ValueFilter</li><li>SingleColumnValueFilter、SingleColumnValueExcludeFilter、PrefixFilter、PageFilter</li><li>多过滤器综合查询FilterList</li><li><a href="https://blog.csdn.net/weixin_43230682/article/details/108169525" target="_blank">HBase过滤器查询</a></li></ul></li><li>用户画像的构建是Hbase做的吗？特征值怎么提取的？</li><li>项目Hbase的RowKey是如何设计的？<ul><li><a href="https://blog.csdn.net/weixin_45462732/article/details/128698296" target="_blank">HBase RowKey设计和实现</a></li></ul></li></ol><h3 id="scala" tabindex="-1">Scala</h3><ol><li>Scala有什么特性</li><li>Scala的class和case class在使用层面有什么区别</li><li>Scala的Option类型的作用与使用方法</li><li>Scala的泛型斜变逆变</li><li>Scala的函数柯里化了解吗？优点是什么？</li><li>Scala的隐式函数、隐式转换<ul><li><a href="https://www.cnblogs.com/xia520pi/p/8745923.html" target="_blank">不是太了解所以查了一下资料</a></li></ul></li><li>Scala用过哪些函数<ul><li><a href="https://blog.csdn.net/qq_47932841/article/details/108718413" target="_blank">Scala常用函数</a></li><li><a href="https://blog.csdn.net/qq_41704237/article/details/107750808" target="_blank">scala常用函数大全</a></li></ul></li><li>Scala中的String是可变的吗？<ul><li>String是一个不可变的对象，所以该对象不可被修改，这就意味着你如果修改字符串就会产生一个新的字符串对象，但其他对象，如数组就是可变的对象</li><li>String对象是不可变的，如果你需要创建一个可以修改的字符串，可以使用<code>String Builder</code>类</li><li><a href="https://blog.csdn.net/pipisorry/article/details/52902348" target="_blank">Scala:字符串</a></li></ul></li><li>Scala是否可以多继承<ul><li>Scala中的多重继承由特质（trait）实现并遵循线性化规则</li><li>在多重继承中，如果一个特质已经显式扩展了一个类，则混入该特质的类必须是之前特质混入的类的子类</li><li>这意味着当混入一个已扩展了别的类的特质时，他们必须拥有相同的父类</li></ul></li></ol><h3 id="spark" tabindex="-1">Spark</h3><ol><li>你了解Spark的序列化吗？Spark提供了哪些序列化类？</li><li>Spark中RDD持久化了解过吗？</li><li><em>Spark算子分类？常用算子？怎么使用的？</em>  <strong>高频</strong></li><li>Spark的惰性计算机制了解过吗？（懒加载）<ul><li><a href="https://www.jianshu.com/p/2887fc61ad46" target="_blank">Spark的惰性求值</a></li></ul></li><li><em>工作中如何进行Spark优化？</em> <strong>高频</strong><ul><li><a href="https://blog.csdn.net/AntKengElephant/article/details/96311384" target="_blank">Spark性能优化的10大问题及其解决方案</a></li><li><a href="https://blog.csdn.net/qq_16146103/article/details/108180964" target="_blank">Spark 出现的问题及其解决方案</a></li><li><a href="https://mp.weixin.qq.com/s/sjTJ6BjUaklmIHZpxqNR8g" target="_blank">Spark内存管理详解</a></li><li><a href="https://mp.weixin.qq.com/s/2ETmGCdgts3P8YdXjQxJFg" target="_blank">详解SparkSQL并行执行多个Job</a></li><li><a href="https://mp.weixin.qq.com/s/S90onC4sOJ77kwUc4SNvvg" target="_blank">Spark的两种核心Shuffle详解（面试常问，工作常用）</a></li><li><a href="https://mp.weixin.qq.com/s/2yT4QGIc7XTI62RhpYEGjw" target="_blank">Spark性能调优-Shuffle调优及故障排除篇</a></li><li><a href="https://mp.weixin.qq.com/s/DZ0kDi4t11SGwtnZguI_yg" target="_blank">Spark性能调优-RDD算子调优篇</a></li></ul></li><li>Spark的Stage和Task的划分？Task数目由什么决定？</li><li>Spark的宽窄依赖了解过吗？</li><li>Spark如何查看日志和排查报错问题？<ul><li><a href="https://blog.csdn.net/qq_33588730/article/details/109353336" target="_blank">如何查看Spark日志与排查报错问题</a></li></ul></li><li>工作中跟有没有遇到到Spark数据倾斜，如何处理的？<ul><li><a href="https://mp.weixin.qq.com/s/oho_WMbe-wy5vZkSZIMDhA" target="_blank">详解 Spark 数据倾斜及解决方案</a></li></ul></li><li>Spark Streaming怎么保证精准的消费？</li><li>Spark在工作中遇到什么样的难点？如何解决？</li></ol><h4 id="spark%E9%9D%A2%E8%AF%95%E8%B5%84%E6%96%99%E5%90%88%E9%9B%86" tabindex="-1">Spark面试资料合集</h4><ol><li><a href="https://mp.weixin.qq.com/s/Lx3kWDs_XjhuyibX8dhFMQ" target="_blank">Spark面试八股文</a></li><li><a href="https://mp.weixin.qq.com/s/aohvYfKWwtIUi63qII5jYw" target="_blank">Spark吐血整理</a></li><li><a href="https://mp.weixin.qq.com/s/LnmeBdjc8uYrrKiVKAzL-A" target="_blank">Spark学习笔记</a></li><li><a href="https://mp.weixin.qq.com/s/V08jUJ4cMCtxVJQ8JbUdCA" target="_blank">上万字详解Spark Core</a></li></ol><h3 id="kafka" tabindex="-1">Kafka</h3><ol><li>简单介绍下kafka的核心概念及个人理解</li><li>Kafka在数据传输过程中遇到重复数据怎么处理</li><li>Kafka在使用过程中如何保证数据不丢失</li><li>Kafka中的ack含义是什么<ul><li><a href="https://blog.csdn.net/lbh199466/article/details/89917693" target="_blank">Kafka的ACK含义</a></li></ul></li></ol><h4 id="kafka%E9%9D%A2%E8%AF%95%E5%85%AB%E9%97%AE" tabindex="-1">Kafka面试八问</h4><p><strong><a href="https://mp.weixin.qq.com/s/_JMfI7tPanF0SHcH1Nxx0g" target="_blank">大厂面试官竟然这么爱问Kafka，一连八个Kafka问题把我问蒙了？</a></strong></p><ol><li>为什么要使用Kafka？</li><li>Kafka消费过的消息如何再消费？</li><li>Kafka的数据是放在磁盘上还是内存上，为什么速度会快？</li><li>Kafka数据怎么保障不丢失？</li><li>采集数据为什么选择Kafka？</li><li>Kafka重启是否会导致数据丢失？</li><li>Kafka宕机了如何解决？</li><li>为什么Kafka不支持读写分离？</li></ol><h3 id="java" tabindex="-1">Java</h3><ol><li>Java的io流分类<ul><li><a href="https://blog.csdn.net/m0_61961937/article/details/123957167" target="_blank">数据流向：输入流/输出流；数据类型：字节流/字符流</a></li></ul></li><li>Java怎么写事务<ul><li><a href="https://www.cnblogs.com/Oh-mydream/p/15768834.html" target="_blank">connection.setAutoCommit(false)</a></li></ul></li><li>MyBatis怎么使用事务<ul><li><a href="https://blog.csdn.net/qq_45756124/article/details/121248797" target="_blank">使用TransactionFactory和Transaction接口</a></li><li><a href="https://blog.csdn.net/qq_32907195/article/details/107378480" target="_blank">只要是对数据库进行任何update操作（update、delete、insert），都一定是在事务中进行的</a></li></ul></li><li>Java的内部类和外部类<ul><li>在Java中，可以将一个类定义在另一个类里面或者一个方法里面，这样的类称为内部类</li><li>内部类一般来说包括这四种：成员内部类、局部内部类、匿名内部类和静态内部类</li><li>静态成员内部类：使用static修饰类</li><li>非静态成员内部类：未用static修饰类，在没有说明是静态成员内部类时，默认成员内部类指的就是非静态成员内部类</li></ul></li><li>Java中全局变量、静态全局变量、静态局部变量和局部变量的区别</li><li>Java中重写与重载之间的区别</li><li>Java中的final关键字</li><li>Java的jvm了解吗<ul><li><a href="https://mp.weixin.qq.com/s/0auWlqdL8dK1Yo1uwHzjmQ" target="_blank">精选大数据面试真题JVM专项</a></li></ul></li><li>多线程，线程启动个数如何确定？最多多少？</li></ol><h3 id="flink" tabindex="-1">Flink</h3><ol><li>Flink-cdc介绍一下<ul><li><a href="https://mp.weixin.qq.com/s/nvhK5VUn1MOXDt3_V8QyWg" target="_blank">基于Flink CDC打通数据实时入湖</a></li></ul></li><li>Flink和Spark的区别是什么？</li><li>你们之前使用Spark做实时，后来为什么使用Flink了？</li><li>Flink的windowapi的分类介绍一下</li><li>Flink常用算子介绍一下<ul><li><a href="https://mp.weixin.qq.com/s/4BVUeXrhSSG0xmOGp5UQog" target="_blank">硬核！一文学完Flink流计算常用算子</a></li></ul></li><li>Flink的cep了解么？怎么使用的？</li><li>Flink的水位线了解吗？可以具体讲讲吗？</li><li>你们Flink主要使用api开发还是sql开发？</li><li>能讲讲Flink双流join是如何实现的吗？</li></ol><h4 id="flink%E9%9D%A2%E8%AF%95%E8%B5%84%E6%96%99%E5%90%88%E9%9B%86" tabindex="-1">Flink面试资料合集</h4><ol><li><a href="https://mp.weixin.qq.com/s/b3Z3ZKGU1kj6CapwsiNiDA" target="_blank">Flink面试八股文</a></li><li><a href="https://mp.weixin.qq.com/s/GCUSaYzGGKM62ibT8g5seA" target="_blank">Flink面试大全总结</a></li><li><a href="https://mp.weixin.qq.com/s/9BbHr5kwcxu6ml0izFbwTQ" target="_blank">Flink高频面试题</a></li><li><a href="https://mp.weixin.qq.com/s/fXJzqQ2s6GRza3cCkgiiTw" target="_blank">Flink学习笔记</a></li></ol><h3 id="clickhouse" tabindex="-1">clickhouse</h3><ol><li>你们有有过clickhouse做join吗？如何保证秒级延迟？</li></ol><h2 id="%E6%B6%89%E5%8F%8A%E5%88%B0%E9%A1%B9%E7%9B%AE%E5%92%8C%E6%95%B0%E4%BB%93%E7%9A%84%E9%97%AE%E9%A2%98" tabindex="-1">涉及到项目和数仓的问题</h2><ol><li>简单介绍一下你们项目中的业务吧</li><li>能结合业务说说你们的数仓怎么搭建的吗？你负责哪些模块？<ul><li><a href="https://mp.weixin.qq.com/s/uUcQZNcRhnK64Al4KIUTeQ" target="_blank">结合公司业务分析离线数仓建设</a></li><li><a href="https://mp.weixin.qq.com/s/BLK9HVAizEgVYB3jLXiQsQ" target="_blank">大数据平台中的企业级数仓建设</a></li></ul></li><li>你们项目中的人员怎么分配的？</li><li>你们的<code>主题</code>是根据什么来划分，为什么这么划分？</li><li>你们源数据大概多少张表？</li><li>你们的日志数据到建立<code>事实表</code>的过程中，主要做了什么？</li><li>你们的<code>维度层</code>是怎么建设的</li><li><em>如果在解析日志文件时遇到很多的硬编码，如何使用维度去解决？</em></li><li>你们的项目中有多少个<code>指标</code>？负责过多少个？怎么编写的？<ul><li><a href="https://mp.weixin.qq.com/s/puEoMCw25E07JePIUtFUmw" target="_blank">数仓中指标-标签，维度-度量，自然键-代理键等常见的概念术语解析</a></li></ul></li><li><em>如果指标出现同义不同名的情况如何解决？</em></li><li><em>阿里的<code>oneData体系</code>有了解吗？</em><ul><li><a href="https://mp.weixin.qq.com/s/4t9_gZsyBxb0HBFz4SfgIQ" target="_blank">数据中台底层逻辑</a></li><li><a href="https://mp.weixin.qq.com/s/2Qd9inws8h14Gw1dKoa45g" target="_blank">大数据实践：数据指标中心的建设思路</a></li><li><a href="https://mp.weixin.qq.com/s/1Aua-Llz20sIDVJk3cuiLw" target="_blank">基于OneData的数据仓库建设</a></li></ul></li><li>你们如何保证<code>数据质量</code>的？你日常遇到最多的<code>数据质量</code>问题是什么？<ul><li><a href="https://mp.weixin.qq.com/s/p9-2J92QqaNF5vWtNrE_3Q" target="_blank">数据仓库之数据质量建设</a></li></ul></li><li>你的项目中<code>离线任务</code>有多少个？任务执行的时间是什么时候？</li><li>你的项目中<code>数据量，日活，漏斗分析</code>大概是多少？以及其他分析的方向？</li><li>你们项目中的<code>业务数据清洗</code>是怎么做的？</li><li>数仓的<code>整体架构</code>是什么样的？数仓是<code>如何分层</code>的？<ul><li><a href="https://mp.weixin.qq.com/s/lNE14-u2Gw2JoZybC1dhSw" target="_blank">数仓建设 | ODS、DWD、DWM等理论实战</a></li><li><a href="https://mp.weixin.qq.com/s/JyqUdApNfTIT9-k6oc2P2A" target="_blank">关于数仓建设及数据治理的超全概括</a></li><li><a href="https://mp.weixin.qq.com/s/UXusmwpQ0FnII-4mpLo_Xw" target="_blank">数仓建设 | 谁说ODS层就是简单的数据同步？</a></li></ul></li><li>介绍一下项目中的<code>数据流向</code><ul><li><a href="https://www.bilibili.com/video/BV1ED4y1R7qM?p=2" target="_blank">可以参考这种用户画像数据流动</a></li></ul></li><li>介绍一下项目中<code>处理后的数据用途</code>？</li><li>数仓中的<code>增量表</code>和<code>全量表</code>分别是怎么做的？有没有用过<code>拉链表</code>？</li><li>工作中有没有遇到的<code>数据倾斜</code>？如何处理的？</li><li>工作中有没有遇到过<code>断点续传</code>的问题？怎么处理的 <strong>这个问题具体技术具体处理</strong></li><li>你的<code>数据建模经验</code>介绍一下？<ul><li><a href="https://mp.weixin.qq.com/s/1PbG1jBDECvnvuyNCl630g" target="_blank">数据建模知多少？</a></li><li><a href="https://mp.weixin.qq.com/s/-W4Fh3fDhMJTyj92RuNOaw" target="_blank">通俗易懂数仓建模—Inmon范式建模与Kimball维度建模</a></li><li><a href="https://zhuanlan.zhihu.com/p/137454121" target="_blank">浅谈数仓模型（维度建模）</a></li></ul></li><li><code>Java框架</code>在你们项目中的应用？</li><li>你在<em>实时开发</em>的过程中遇到什么问题？如何解决的？</li><li>你们的<em>实时模型</em>是如何进行优化的？怎么评估它是否是一个优质的模型？</li><li>你们的<code>任务监控</code>有做过吗？主要监控什么？</li><li>你们如何保证<code>数据的准确性</code>？</li><li>埋点数据缺失怎么处理，埋点数据相关的表示如何设计的？</li><li>零点漂移如何解决？</li><li>有做过用户路径模型吗？每条路径的转化率是多少？（是不是类似页面单跳率那种）</li><li>你们的OLAP引擎主要用的什么？为什么这样选型？</li><li>能聊聊<code>端到端的一致性</code>和<code>精准一次消费</code>吗？</li></ol><h2 id="%E5%B7%A5%E4%BD%9C%E5%86%85%E5%AE%B9%E7%9A%84%E9%97%AE%E9%A2%98" tabindex="-1">工作内容的问题</h2><ol><li>你们的需求周期一般是多久？拿到需求之后怎么分析？</li><li>在完成需求的过程中，有没有考虑过数仓的通用性，你们是如何体现的？</li><li>你在工作中的最大收获是什么，带给你什么样的能力？</li><li>你们的工作强度怎么样，能接受加班吗？</li><li>如果一周内让你做十个紧急的需求，你会怎么办？</li><li>你们部门之间是直接进行对接吗？</li><li>有没有反驳过产品提出的需求，如果不合理怎么办？</li></ol><h2 id="%E5%90%84%E7%A7%8D%E7%BB%84%E4%BB%B6%E9%9C%80%E8%A6%81%E5%AD%A6%E5%88%B0%E4%BB%80%E4%B9%88%E6%A0%B7%E7%9A%84%E7%A8%8B%E5%BA%A6" tabindex="-1">各种组件需要学到什么样的程度</h2><h3 id="hadoop%E5%AD%A6%E4%B9%A0%E5%88%B0%E4%BB%80%E4%B9%88%E6%A0%B7%E7%9A%84%E7%A8%8B%E5%BA%A6" tabindex="-1">hadoop学习到什么样的程度</h3><ol><li>hdfs、mapreduce、yarn 基本原理即可</li><li>各种面试基本题完成即可<ul><li>MapReduce的执行过程，如何进行一个文件的计算，shuffle的过程（不需要手写mapreduce或者用java写mapreduce）</li><li>HDFS的读写流程</li><li>Yarn的任务提交流程，如何查看任务日志和报错信息</li></ul></li><li>大多数公司可能都进入spark阶段了</li></ol><h3 id="spark%E5%AD%A6%E4%B9%A0%E5%88%B0%E4%BB%80%E4%B9%88%E6%A0%B7%E7%9A%84%E7%A8%8B%E5%BA%A6" tabindex="-1">spark学习到什么样的程度</h3><ol><li>主要为了优化mapreduce2的一些问题，利用内存计算，减少磁盘IO</li><li>spark streaming + hbase 离线实时 -&gt; 使用flink搭建一整套实时</li><li>熟悉spark开发的api和常用的算子</li><li>熟悉spark调优</li><li>spark的应用：批处理，流处理流计算（flink和sparkstreaming），数据分析，图形计算</li></ol><h3 id="spark%E9%9D%A2%E8%AF%95%E9%87%8D%E7%82%B9" tabindex="-1">spark面试重点</h3><ol><li>一些八股文知识点，基本原理，是高频且必须熟练知晓的，核心原理的流程和图</li><li>spark常见报错的解决方案<ul><li>real spark冲突怎么解决</li><li>om怎么处理，driver的om和excute的om分别怎么处理</li></ul></li><li>参数调优，集群层面调优，应用层面调优<ul><li>为什么这么做</li><li>有没有更好的方案</li></ul></li><li>sparksql调优<ul><li>调优场景</li><li>怎么发现问题的</li><li>发现问题后怎么定位问题的</li><li>定位问题后怎么解决的</li><li>解决之后达到什么样的一个效果</li><li>还有哪些更好的一些优化方案吗</li></ul></li><li>根据业务场景现场写sql，根据数仓的理论优化sql<ul><li>常规的业务逻辑，join，for函数，截取字符串，嵌套多个高级函数</li></ul></li><li>企业开发使用场景的解决方案<ul><li>udf管理：统一仓库管理还是自己提交，怎么解决相同含义的sql function被重复的udf提交</li><li>thift server怎么用的，怎么用的，怎么解决多租户，怎么解决权限，怎么解决负载均衡</li></ul></li><li>spark平台和二次开发，源码，遇到的问题详细问</li><li>面试流程相关<ul><li>10-20分钟问项目</li><li>20分钟问编程基础</li><li>20分钟问组件</li><li>spark问20分钟左右</li></ul></li></ol><h3 id="spark%E7%95%85%E8%B0%88" tabindex="-1">spark畅谈</h3><ol><li>小公司大部分业务场景是离线开发，实时场景也较少</li><li>大公司80%离线，20%实时</li><li>sparkstreaming批处理比flink批处理更快，性能差百分之五十左右，spark使用门槛较低</li><li>入职后可以想老员工讨教经验，核心组件</li><li>博客快速浏览一些内容</li><li>官网提供的一些例子</li></ol><h2 id="%E6%9C%AA%E6%9D%A5%E6%8A%80%E6%9C%AF%E8%B6%8B%E5%8A%BF" tabindex="-1">未来技术趋势</h2><ol><li><a href="https://mp.weixin.qq.com/s/My8ZWES5MrT-2wgU9UUWsQ" target="_blank">万字详解数据仓库、数据湖、数据中台和湖仓一体</a></li><li><a href="https://mp.weixin.qq.com/s/ulXyZ4wWOCXr21hWUynRXw" target="_blank">一文读懂大数据实时计算</a></li><li><a href="https://mp.weixin.qq.com/s/USl8UJ0XKccJSy9cdlLThQ" target="_blank">20000字详解大厂实时数仓建设</a></li></ol><h2 id="%E9%99%84%E5%BD%95" tabindex="-1">附录</h2><h3 id="%E9%9D%A2%E8%AF%95%E8%B5%84%E6%96%99%E6%80%BB%E7%BB%93" tabindex="-1">面试资料总结</h3><ol><li><a href="https://mp.weixin.qq.com/s/Ivp4pjmgWVP-STkP32XjUQ" target="_blank">大数据面试吹牛草稿V2.0</a></li><li><a href="https://mp.weixin.qq.com/s/Yc4C-rnsBdI4gTZMS0zHnA" target="_blank">史上最全大数据面试题​V3.1</a></li><li><a href="https://mp.weixin.qq.com/s/WnxCEJjNkxxjsTbg0lrd3A" target="_blank">精选大数据面试真题10道</a></li></ol><h3 id="%E5%A4%A7%E5%8E%82%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93" tabindex="-1">大厂面试总结</h3><ol><li><a href="https://mp.weixin.qq.com/s/OJOVN7a3OVjkF6iwoVBMIg" target="_blank">字节跳动大数据开发面试题-附答案</a></li><li><a href="https://mp.weixin.qq.com/s/UeTxsqHcEkwQG2lW_7F6sA" target="_blank">美团优选大数据开发岗面试真题-附答案详细解析</a></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llBigData/blob/main/interview/temp/temp-result.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[MySQL在工作中的优化方案总结]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/mysql-optimize" />
                <id>tag:https://fx67ll.xyz,2022-05-11:mysql-optimize</id>
                <published>2022-05-11T14:42:36+08:00</published>
                <updated>2025-07-02T11:29:46+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>MySQL查询性能优化是一个很大的课题，往往需要结合实际情况来制定优化策略，一般的步骤不外乎就是先<code>explain</code>分析，然后尽可能地利用索引，避免全表扫描，避免索引失效等</p><h2 id="%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BB%93%E6%9E%84%E4%BC%98%E5%8C%96" tabindex="-1">数据库结构优化</h2><ol><li>最小数据长度，一般说来数据库的表越小，那么它的查询速度就越快，因此为了提高表的效率，应该将表的字段设置的尽可能小，<br />比如身份证号，可以设置为<code>char(18)</code>就不要设置为<code>varchar(18)</code></li><li>用最简单数据类型，能使用<code>int</code>类型就不要使用<code>varchar</code>类型，因为<code>int</code>类型比<code>varchar</code>类型的查询效率更高</li><li>尽量少定义<code>text</code>类型<code>text</code>类型的查询效率很低，如果必须要使用<code>text</code>定义字段，<br />可以把此字段分离成子表，需要查询此字段时使用联合查询，这样可以提高主表的查询效率</li></ol><h2 id="sql%E4%BC%98%E5%8C%96" tabindex="-1">SQL优化</h2><ol><li>尽量用单表查询，禁止多于三表的<code>join</code><ul><li>MySQL其实在设计上是让连接和断开都很轻量级，在返回一个小的查询结果方面很高效</li><li>如果想用一个复杂的查询，而这个查询涉及了多个表的关联，那其实性能还远不如将这个查询分解成的多个简单查询</li><li>对于一个大查询可以采用分而治之，将大查询切分成小查询，每个查询功能完全一样，只完成一小部分，每次只返回一小部分查询结果</li><li>将MySQL多表关联查询拆分成多个单表查询，然后将查询结果在应用程序逻辑层进行处理，可以提升性能</li></ul></li><li>禁止使用<code>selct * from table</code>，尽量只获取需要的字段</li><li>避免多余的排序，因为涉及到排序，会增加大量的 CPU 运算，加大资源消耗及延迟</li><li>全模糊查询无法使用索引，尽量避免，因为索引是从左到右</li><li>使用<code>in</code>代替<code>or</code></li><li>禁止隐式转换</li><li>禁止使用负向查询，例如<code>not in</code>、<code>!=</code>、<code>not like</code></li><li>尽量使用<code>union all</code>代替<code>union</code><ul><li>union 对两个结果集进行并集操作，不包括重复行，相当于distinct，同时进行默认规则的排序，涉及到排序，会增加大量的 CPU 运算，加大资源消耗及延迟</li><li>union all 对两个结果集进行并集操作，包括重复行，即所有的结果全部显示，不管是不是重复</li></ul></li><li>尽量早过滤，比如尽早使用where、limit、order by等等到子查询当中以减少数据量</li><li>如果希望知道结果集的行数，最好使用<code>count(*)</code>，而不是<code>count(结果集中的某一列)</code><ul><li>字段有索引：count(*) ≈ count(1) &gt; count(字段) &gt; count(主键 id)</li><li>字段无索引：count(*) ≈ count(1) &gt; count(主键 id) &gt; count(字段)</li></ul></li></ol><h2 id="%E7%B4%A2%E5%BC%95%E4%BC%98%E5%8C%96" tabindex="-1">索引优化</h2><h3 id="%E5%A6%82%E6%9E%9C%E7%B4%A2%E5%BC%95%E6%89%AB%E6%8F%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E8%B6%85%E8%BF%8730%25%EF%BC%8C%E9%82%A3%E4%B9%88%E7%B4%A2%E5%BC%95%E4%BC%9A%E5%A4%B1%E6%95%88%EF%BC%8C%E5%8F%98%E6%88%90%E5%85%A8%E8%A1%A8%E6%89%AB%E6%8F%8F%EF%BC%8C%E9%81%BF%E5%85%8D%E7%BB%99%E9%80%89%E6%8B%A9%E6%80%A7%E7%9A%84%E4%BD%8E%E7%9A%84%E5%AD%97%E6%AE%B5%E5%BB%BA%E7%AB%8B%E7%B4%A2%E5%BC%95" tabindex="-1">如果索引扫描的数据超过30%，那么索引会失效，变成全表扫描，避免给选择性的低的字段建立索引</h3><h3 id="%E5%AF%B9%E7%B4%A2%E5%BC%95%E8%BF%9B%E8%A1%8C%E5%87%BD%E6%95%B0%E6%93%8D%E4%BD%9C%E4%B9%8B%E5%90%8E%EF%BC%8C%E7%B4%A2%E5%BC%95%E4%BC%9A%E5%A4%B1%E6%95%88%EF%BC%8C%E6%89%A7%E8%A1%8C%E7%9A%84%E6%98%AF%E5%85%A8%E8%A1%A8%E6%9F%A5%E8%AF%A2" tabindex="-1">对索引进行函数操作之后，索引会失效，执行的是全表查询</h3><pre><code class="language-sql">CREATE TABLE t1(id INT NOT NULL PRIMARY KEY,i INT,dt DATE);CREATE INDEX idx1 ON t1(dt);EXPALINSELECT *FROM t1WHERE YEAR(dt) = &#39;2022&#39;;# 优化替换为索引的范围扫描，找到值之后再通过回表id查找数据SELECT *FROM t1WHERE dt BETWEEN date &#39;2022-01-01&#39; AND date &#39;2022-12-31&#39;;</code></pre><h3 id="%E5%A4%9A%E4%B8%AA%E7%B4%A2%E5%BC%95%EF%BC%8C%E4%BD%BF%E7%94%A8%E6%9F%A5%E8%AF%A2%E8%B0%93%E8%AF%8Dwhere%E5%92%8C%E6%8E%92%E5%BA%8F%E8%B0%93%E8%AF%8Dorder-by%E5%9D%87%E4%B8%8D%E4%BC%9A%E5%BD%B1%E5%93%8D%E7%B4%A2%E5%BC%95" tabindex="-1">多个索引，使用查询谓词<code>where</code>和排序谓词<code>order by</code>均不会影响索引</h3><pre><code class="language-sql">CREATE TABLE t2(id INT NOT NULL PRIMARY KEY,i INT,dt DATE,v VARCHAR(50));CREATE INDEX idx2 ON t2(i, dt);EXPALINSELECT *FROM t2WHERE i = 99ORDER BY dt DESCLIMIT 5;</code></pre><h3 id="%E5%A4%9A%E4%B8%AA%E7%B4%A2%E5%BC%95%EF%BC%8C%E4%BD%BF%E7%94%A8where%E6%9D%A1%E4%BB%B6%E4%BC%9A%E6%9C%89%E6%9C%80%E5%B7%A6%E5%8C%B9%E9%85%8D%E5%8E%9F%E5%88%99" tabindex="-1">多个索引，使用<code>where条件</code>会有最左匹配原则</h3><pre><code class="language-sql">CREATE TABLE t3(id INT NOT NULL PRIMARY KEY,col1 INT,col2 INT,col3 VARCHAR(50));CREATE INDEX idx3 ON t3(col1, col2);# 有最左的第一个列col1，所以该查询会使用索引查询  EXPALINSELECT *FROM t3WHERE col1 = 99AND col2 = 10;# 无最左的第一个列col1，所以该查询不会使用索引查询 EXPALINSELECT *FROM t3WHERE col2 = 10;</code></pre><h3 id="%E5%AF%B9%E4%BA%8E%E7%B4%A2%E5%BC%95%E4%BD%BF%E7%94%A8%E5%89%8D%E5%90%8E%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%9D%87%E6%A8%A1%E7%B3%8A%E6%9F%A5%E8%AF%A2%EF%BC%8C%E4%BC%9A%E6%89%A7%E8%A1%8C%E5%85%A8%E8%A1%A8%E6%89%AB%E6%8F%8F" tabindex="-1">对于索引使用前后字符串均模糊查询，会执行全表扫描</h3><pre><code class="language-sql">CREATE TABLE t4(id INT NOT NULL PRIMARY KEY,col1 INT,col2 VARCHAR(50));CREATE INDEX idx4 ON t4(col2);# 索引扫描是对每个索引进行从左到右处理  EXPALINSELECT *FROM t4WHERE col2 LIKE &quot;%sql%&quot;;# 如果前面确定后面模糊查询，那么就会执行索引扫描  EXPALINSELECT *FROM t4WHERE col2 LIKE &quot;sql%&quot;;</code></pre><h3 id="%E5%A4%9A%E4%B8%AA%E7%B4%A2%E5%BC%95%EF%BC%8C%E6%9F%A5%E8%AF%A2%E4%B8%AD%E6%9C%89%E5%AD%97%E6%AE%B5%E6%97%A0%E7%B4%A2%E5%BC%95%EF%BC%8C%E4%BC%9A%E5%AF%BC%E8%87%B4%E8%A6%86%E7%9B%96%E7%B4%A2%E5%BC%95" tabindex="-1">多个索引，查询中有字段无索引，会导致覆盖索引</h3><pre><code class="language-sql">CREATE TABLE t5(id INT NOT NULL PRIMARY KEY,col1 INT,col2 INT,col5 VARCHAR(50));CREATE INDEX idx5 ON t5(col1, col3);# 第一种查询性能高于第二种# 第一种查询EXPALINSELECT col3,COUNT(*)FROM t35WHERE col1 = 99GROUP BY col3;# 第二种查询EXPALINSELECT col3,COUNT(*)FROM t35WHERE col1 = 99AND col2 = 10GROUP BY col3;</code></pre><h2 id="%E7%B3%BB%E7%BB%9F%E7%A1%AC%E4%BB%B6%E4%BC%98%E5%8C%96" tabindex="-1">系统硬件优化</h2><p>MySQL对硬件的要求主要体现在三个方面：磁盘、网络和内存</p><ol><li>磁盘<ul><li>磁盘寻道能力（磁盘I/O）,以目前高转速SCSI硬盘(7200转/秒)为例，这种硬盘理论上每秒寻道7200次，这是物理特性决定的，没有办法改变。</li><li>磁盘应该尽量使用有高性能读写能力的磁盘，比如固态硬盘，这样就可以减少 I/O 运行的时间，从而提高了 MySQL 整体的运行效率。</li><li>磁盘也可以尽量使用多个小磁盘而不是一个大磁盘，因为磁盘的转速是固定的，有多个小磁盘就相当于拥有多个并行运行的磁盘一样。</li></ul></li><li>网络<ul><li>保证网络宽带的通畅（低延迟）以及够大的网络带宽是 MySQL 正常运行的基本条件，如果条件允许的话也可以设置多个网卡，以提高网络高峰期 MySQL 服务器的运行效率。</li><li>DNS配置尽量使用<code>skip-name-resolve</code>来减少因解析带来的不必要麻烦。</li><li>检查网络的<code>ping</code>丢包率。</li><li>通过优化<code>/etc/sysctl.cnf </code>中的网络参数，提升性能。</li></ul></li><li>内存<ul><li>MySQL服务器的内存越大，那么存储和缓存的信息也就越多，而内存的性能是非常高的，从而提高了整个MySQL的运行效率。</li></ul></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llBigData/blob/main/sql/blog/sql-optimize.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Hive在工作中的调优总结]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/hive-optimize-inwork" />
                <id>tag:https://fx67ll.xyz,2022-05-03:hive-optimize-inwork</id>
                <published>2022-05-03T16:59:42+08:00</published>
                <updated>2025-07-03T10:42:02+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>总结了一下在以往工作中，对于<code>Hive SQL</code>调优的一些实际应用，是日常积累的一些优化技巧，如有出入，欢迎在评论区留言探讨~</p><h2 id="%E4%B8%80%E3%80%81explain-%E6%9F%A5%E7%9C%8B%E6%89%A7%E8%A1%8C%E8%AE%A1%E5%88%92" tabindex="-1">一、EXPLAIN 查看执行计划</h2><h2 id="%E4%BA%8C%E3%80%81%E5%BB%BA%E8%A1%A8%E4%BC%98%E5%8C%96" tabindex="-1">二、建表优化</h2><h3 id="2.1-%E5%88%86%E5%8C%BA" tabindex="-1">2.1 分区</h3><ol><li>分区表基本操作，partitioned</li><li>二级分区</li><li>动态分区</li></ol><h3 id="2.2-%E5%88%86%E6%A1%B6" tabindex="-1">2.2 分桶</h3><ol><li>分桶表基本操作，clustered</li><li>分桶表主要是抽样查询，找出具有代表性的结果</li></ol><h3 id="2.3-%E9%80%89%E6%8B%A9%E5%90%88%E9%80%82%E7%9A%84%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E5%92%8C%E5%8E%8B%E7%BC%A9%E6%A0%BC%E5%BC%8F" tabindex="-1">2.3 选择合适的文件格式和压缩格式</h3><ol><li>LZO，拉兹罗</li><li>Snappy</li><li>压缩速度快，压缩比高</li></ol><h2 id="%E4%B8%89%E3%80%81hivesql%E8%AF%AD%E6%B3%95%E4%BC%98%E5%8C%96" tabindex="-1">三、HiveSQL语法优化</h2><h3 id="3.1-%E5%8D%95%E8%A1%A8%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96" tabindex="-1">3.1 单表查询优化</h3><ol><li><p>列裁剪和分区裁剪，全表和全列扫描效率都很差，生产环境绝对不要使用<code>SELECT *</code>，所谓列裁剪就是在查询时只读取需要的列，分区裁剪就是只读取需要的分区</p><ul><li>与列裁剪优化相关的配置项是<code>hive.optimize.cp</code>，默认是true</li><li>与分区裁剪优化相关的则是<code>hive.optimize.pruner</code>，默认是true</li><li>在<code>HiveSQL</code>解析阶段对应的则是<code>ColumnPruner</code>逻辑优化器</li></ul></li><li><p>Group By 配置调整，<code>map</code>阶段会把同一个<code>key</code>发给一个<code>reduce</code>，当一个<code>key</code>过大时就倾斜了，可以开启<code>map</code>端预聚合，可以有效减少<code>shuffle</code>数据量并</p><pre><code class="language-"># 是否在map端聚合，默认为trueset hive.map.aggr = true;# 在map端聚合的条数set hive.groupby.mapaggr.checkintervel = 100000;# 在数据倾斜的时候进行均衡负载（默认是false），开启后会有 两个&#96;mr任务&#96;。# 当选项设定为true时，第一个 &#96;mr任务&#96; 会将map输出的结果随机分配到&#96;reduce&#96;，# 每个&#96;reduce&#96;会随机分布到&#96;reduce&#96;上，这样的处理结果是会使相同的&#96;group by key&#96;分到不同的&#96;reduce&#96;上。# 第二个 &#96;mr任务&#96; 再根据预处理的结果按&#96;group by key&#96;分到&#96;reduce&#96;上，# 保证相同&#96;group by key&#96;的数据分到同一个&#96;reduce&#96;上。# *切记！！！* # 这样能解决数据倾斜，但是不能让运行速度更快  # 在数据量小的时候，开始数据倾斜负载均衡可能反而会导致时间变长  # 配置项毕竟是死的，单纯靠它有时不能根本上解决问题# 因此还是建议自行了解数据倾斜的细节，并优化查询语句  set hive.groupby.skewindata = true;</code></pre></li><li><p>Vectorization，矢量计算技术，通过设置批处理的增量大小为1024行单次来达到比单行单次更好的效率</p><pre><code class="language-"># 开启矢量计算  set hive.vectorized.execution.enabled = true;# 在reduce阶段开始矢量计算  set hive.vectorized.execution.reduce.enabled = true;</code></pre></li><li><p>多重模式，一次读取多次插入，同一张表的插入操作优化成先<code>from table</code>再<code>insert</code></p></li><li><p>in/exists或者join用<code>left semi join</code>代替（为什么替代扩展一下~）</p></li></ol><h3 id="3.2-%E5%A4%9A%E8%A1%A8%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96" tabindex="-1">3.2 多表查询优化</h3><ol><li><p>CBO优化，成本优化器，代价最小的执行计划就是最好的执行计划</p><ul><li>join的时候表的顺序关系，前面的表都会被加载到内存中，后面的表进行磁盘扫描</li><li>通过<code>hive.cbo.enable</code>，自动优化hivesql中多个join的执行顺序</li><li>可以通过查询一下参数，这些一般都是true，无需修改</li></ul><pre><code class="language-">set hive.cbo.enable = true;set hive.compute.query.using.stats = true;set hive.stats.fetch.column.stats = true;set hive.stats.fetch.partition.stats = true;</code></pre></li><li><p>谓词下推（<em><strong>非常关键的一个优化</strong></em>），将<code>sql</code>语句中的<code>where</code>谓词逻辑都尽可能提前执行，减少下游处理的数据量，<br />在关系型数据库如MySQL中，也有<code>谓词下推（Predicate Pushdown，PPD）</code>的概念，<br />它就是将<code>sql</code>语句中的<code>where</code>谓词逻辑都尽可能提前执行，减少下游处理的数据量</p><pre><code class="language-"># 这个设置是默认开启的 # 如果关闭了但是cbo开启，那么关闭依然不会生效 # 因为cbo会自动使用更为高级的优化计划  # 与它对应的逻辑优化器是PredicatePushDown# 该优化器就是将OperatorTree中的FilterOperator向上提set hive.optimize.pdd = true;# 举个例子# 对forum_topic做过滤的where语句写在子查询内部，而不是外部select a.uid,a.event_type,b.topic_id,b.titlefrom calendar_record_log aleft outer join (  select uid,topic_id,title from forum_topic  where pt_date = 20220108 and length(content) &gt;= 100) b on a.uid = b.uidwhere a.pt_date = 20220108 and status = 0;</code></pre></li><li><p>Map Join，<code>map join</code>是指将<code>join</code>操作两方中比较小的表直接分发到各个<code>map</code>进程的内存中，在<code>map</code>中进行<code>join</code>的操作。<br /><code>map join</code>特别适合<code>大小表join</code>的情况，Hive会将<code>build table</code>和<code>probe table</code>在<code>map</code>端直接完成<code>join</code>过程，消灭了<code>reduce</code>，减少<code>shuffle</code>，所以会减少开销</p><ul><li><code>set hive.auto.convert.join = true</code>，配置开启，默认是true</li><li><em>注意！！！</em> 如果执行<code>小表join大表</code>，小表作为主连接的主表，所有数据都要写出去，此时会走<code>reduce</code>阶段，<code>mapjoin</code>会失效</li><li><code>大表join小表</code>不受影响，上一条的原因主要是因为<code>小表join大表</code>的时候，<code>map</code>阶段不知道<code>reduce</code>的结果其他<code>reduce</code>是否有，</li><li>所以必须在最后<code>reduce</code>聚合的时候再处理，就产生了<code>reduce</code>的开销</li></ul><pre><code class="language-"># 举个例子# 在最常见的&#96;hash join&#96;方法中，一般总有一张相对小的表和一张相对大的表，# 小表叫&#96;build table&#96;，大表叫&#96;probe table&#96;  # Hive在解析带join的SQL语句时，会默认将最后一个表作为&#96;probe table&#96;，# 将前面的表作为&#96;build table&#96;并试图将它们读进内存  # 如果表顺序写反，&#96;probe table&#96;在前面，引发&#96;OOM（内存不足）&#96;的风险就高了  # 在维度建模数据仓库中，事实表就是&#96;probe table&#96;，维度表就是&#96;build table&#96;  # 假设现在要将日历记录事实表和记录项编码维度表来&#96;join&#96;  select a.event_type,a.event_code,a.event_desc,b.upload_timefrom calendar_event_code ainner join (  select event_type,upload_time from calendar_record_log  where pt_date = 20220108) b on a.event_type = b.event_type;</code></pre></li><li><p>Map Join，大表和大表的<code>MapReduce任务</code>，可以使用<code>SMB Join</code></p><ul><li>直接join耗时会很长，但是根据某字段分桶后，两个大表每一个桶就是一个小文件，两个表的每个小文件的分桶字段都应该能够一一对应（hash值取模的结果）</li><li>总结就是分而治之，注意两个大表的分桶字段和数量都应该保持一致</li></ul><pre><code class="language-">set hive.optimize.bucketmapjoin = true;set hive.optimeize.bucketmapjoin.sortedmerge = true;hive.input.format = org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat;</code></pre></li><li><p>多表join时key相同，这种情况会将多个<code>join</code>合并为一个<code>mr 任务</code>来处理</p><pre><code class="language-"># 举个例子# 如果下面两个join的条件不相同  # 比如改成a.event_code = c.event_code  # 就会拆成两个MR job计算select a.event_type,a.event_code,a.event_desc,b.upload_timefrom calendar_event_code ainner join (  select event_type,upload_time from calendar_record_log  where pt_date = 20220108) b on a.event_type = b.event_typeinner join (  select event_type,upload_time from calendar_record_log_2  where pt_date = 20220108) c on a.event_type = c.event_type;</code></pre></li><li><p>笛卡尔积，在生产环境中严禁使用</p></li></ol><h3 id="3.3-%E5%85%B6%E4%BB%96%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96" tabindex="-1">3.3 其他查询优化</h3><ol><li><p>Sort By 代替 Order By，HiveQL中的<code>order by</code>与其他<code>sql</code>方言中的功能一样，就是将结果按某字段全局排序，这会导致所有<code>map</code>端数据都进入一个<code>reducer</code>中，<br />在数据量大时可能会长时间计算不完。如果使用<code>sort by</code>，那么还是会视情况启动<code>多个reducer进行排序</code>，并且保证每个<code>reducer</code>内局部有序。<br />为了控制<code>map</code>端数据分配到<code>reducer</code>的<code>key</code>，往往还要配合<code>distribute by</code>一同使用，如果不加<code>distribute by</code>的话，<code>map</code>端数据就会随机分配到<code>reducer</code></p><pre><code class="language-"># 举个例子select uid,upload_time,event_type,record_datafrom calendar_record_logwhere pt_date &gt;= 20220108 and pt_date &lt;= 20220131distribute by uidsort by upload_time desc,event_type desc;</code></pre></li><li><p>Group By代替Distinct，当要统计某一列的去重数时，如果数据量很大，<code>count(distinct)</code>就会非常慢，原因与<code>order by</code>类似，<br /><code>count(distinct)</code>逻辑只会有很少的<code>reducer</code>来处理。但是这样写会启动两个<code>mr任务</code>（单纯distinct只会启动一个），<br />所以要确保数据量大到启动<code>mr任务</code>的<code>overhead</code>远小于计算耗时，才考虑这种方法，当数据集很小或者<code>key</code>的倾斜比较明显时，<code>group by</code>还可能会比<code>distinct</code>慢</p></li></ol><h2 id="%E5%9B%9B%E3%80%81%E6%95%B0%E6%8D%AE%E5%80%BE%E6%96%9C" tabindex="-1">四、数据倾斜</h2><p>注意要和数据过量的情况区分开，数据倾斜是大部分任务都已经执行完毕，但是某一个任务或者少数几个任务，一直未能完成，甚至执行失败，<br />而数据过量，是大部分任务都执行的很慢，这种情况需要通过扩充执行资源的方式来加快速度，大数据编程不怕数据量大，就怕数据倾斜，一旦数据倾斜，严重影响效率</p><h3 id="4.1-%E5%8D%95%E8%A1%A8%E6%90%BA%E5%B8%A6%E4%BA%86-group-by-%E5%AD%97%E6%AE%B5%E7%9A%84%E6%9F%A5%E8%AF%A2" tabindex="-1">4.1 单表携带了 Group By 字段的查询</h3><ol><li>任务中存在<code>group by</code>操作，同时聚合函数为<code>count</code>或<code>sum</code>，单个<code>key</code>导致的数据倾斜可以这样通过设置开启<code>map</code>端预聚合参数的方式来处理<pre><code class="language-"># 是否在map端聚合，默认为trueset hive.map.aggr = true;# 在map端聚合的条数set hive.groupby.mapaggr.checkintervel = 100000;# 有数据倾斜的时候开启负载均衡，这样会生成两个mr任务set hive.groupby.skewindata = true;</code></pre></li><li>任务中存在<code>group by</code>操作，同时聚合函数为<code>count</code>或<code>sum</code>，多个<code>key</code>导致的数据倾斜可以通过增加<code>reduce</code>的数量来处理<ul><li>增加分区可以减少不同分区之间的数据量差距，而且增加的分区时候不能是之前分区数量的倍数，不然会导致取模结果相同继续分在相同分区</li><li>第一种修改方式</li></ul><pre><code class="language-"># 每个reduce处理的数量set hive.exec.reduce.bytes.per.reducer = 256000000;# 每个任务最大的reduce数量set hive.exec.reducers.max = 1009;# 计算reducer数的公式，根据任务的需要调整每个任务最大的reduce数量  N = min（设置的最大数，总数量数/每个reduce处理的数量）</code></pre><ul><li>第二种修改方式</li></ul><pre><code class="language-"># 在hadoop的mapred-default.xml文件中修改set mapreduce.job.reduces = 15;</code></pre></li></ol><h3 id="4.2-%E4%B8%A4%E8%A1%A8%E6%88%96%E5%A4%9A%E8%A1%A8%E7%9A%84-join-%E5%85%B3%E8%81%94%E6%97%B6%EF%BC%8C%E5%85%B6%E4%B8%AD%E4%B8%80%E4%B8%AA%E8%A1%A8%E8%BE%83%E5%B0%8F%EF%BC%8C%E4%BD%86%E6%98%AF-key-%E9%9B%86%E4%B8%AD" tabindex="-1">4.2 两表或多表的 join 关联时，其中一个表较小，但是 key 集中</h3><ol><li>设置参数增加<code>map</code>数量<pre><code class="language-"># join的key对应记录条数超过该数量，会进行分拆  set hive.skewjoin.key = 1000;# 并设置该参数为true，默认是falseset hive.optimize.skewjoin = true;# 上面的参数如果开启了会将计算数量超过阈值的key写进临时文件，再启动另外一个任务做map join  # 可以通过设置这个参数，控制第二个任务的mapper数量，默认10000set hive.skewjoin.mapjoin.map.tasks = 10000;</code></pre></li><li>使用<code>mapjoin</code>，减少<code>reduce</code>从根本上解决数据倾斜，参考<code>HiveSQL语法优化 -&gt; 多表查询优化 -&gt;  Map Join，大表和大表的MapReduce任务，SMB Join</code></li></ol><h3 id="4.3-%E4%B8%A4%E8%A1%A8%E6%88%96%E5%A4%9A%E8%A1%A8%E7%9A%84-join-%E5%85%B3%E8%81%94%E6%97%B6%EF%BC%8C%E6%9C%89-null%E5%80%BC-%E6%88%96-%E6%97%A0%E6%84%8F%E4%B9%89%E5%80%BC" tabindex="-1">4.3 两表或多表的 join 关联时，有 Null值 或 无意义值</h3><p>这种情况很常见，比如当<code>事实表是日志类数据</code>时，往往会有一些项没有记录到，我们视情况会将它置为<code>null</code>，或者<code>空字符串</code>、<code>-1</code>等，<br />如果缺失的项很多，在做<code>join</code>时这些空值就会非常集中，拖累进度，因此，若不需要空值数据，就提前写where语句过滤掉，<br />需要保留的话，将空值key用随机方式打散，例如将<em>用户ID为null的记录随机改为负值</em>：</p><pre><code class="language-">select a.uid,a.event_type,b.nickname,b.agefrom (  select   (case when uid is null then cast(rand()*-10240 as int) else uid end) as uid,  event_type from calendar_record_log  where pt_date &gt;= 20220108) a left outer join (  select uid,nickname,age from user_info where status = 4) b on a.uid = b.uid;</code></pre><h3 id="4.4-%E4%B8%A4%E8%A1%A8%E6%88%96%E5%A4%9A%E8%A1%A8%E7%9A%84-join-%E5%85%B3%E8%81%94%E6%97%B6%EF%BC%8C%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E4%B8%8D%E7%BB%9F%E4%B8%80" tabindex="-1">4.4 两表或多表的 join 关联时，数据类型不统一</h3><p>比如<code>int</code>类型和<code>string</code>类型进行关联，关联时候以小类型作为分区，这里<code>int</code>、<code>string</code>会到一个<code>reduceTask</code>中，如果数据量多，会造成数据倾斜</p><pre><code class="language-"># 可以通过转换为同一的类型来处理cast(user.id as string)</code></pre><h3 id="4.5-%E5%8D%95%E7%8B%AC%E5%A4%84%E7%90%86%E5%80%BE%E6%96%9Ckey" tabindex="-1">4.5 单独处理倾斜key</h3><p>这其实是上面处理空值方法的拓展，不过倾斜的<code>key</code>变成了有意义的，一般来讲倾斜的<code>key</code>都很少，我们可以将它们抽样出来，<br />对应的行单独存入临时表中，然后打上一个较小的随机数前缀（比如<code>0~9</code>），最后再进行聚合</p><h2 id="%E4%BA%94%E3%80%81hive-job-%E4%BC%98%E5%8C%96" tabindex="-1">五、Hive Job 优化</h2><h3 id="5.1-hive-map-%E4%BC%98%E5%8C%96" tabindex="-1">5.1 Hive Map 优化</h3><h4 id="5.1.1-map%E6%95%B0%E9%87%8F%E5%A4%9A%E5%B0%91%E7%9A%84%E5%BD%B1%E5%93%8D" tabindex="-1">5.1.1 Map数量多少的影响</h4><ol><li>Map数过大<ul><li><code>map</code>阶段输出文件太小，产生大量小文件</li><li>初始化和创建<code>map</code>的开销很大</li></ul></li><li>Map数太小<ul><li>文件处理或查询并发度小，<code>Job</code>执行时间过长</li><li>大量作业时，容易堵塞集群</li></ul></li></ol><h4 id="5.1.2-%E6%8E%A7%E5%88%B6map%E6%95%B0%E7%9A%84%E5%8E%9F%E5%88%99" tabindex="-1">5.1.2 控制Map数的原则</h4><p>根据实际情况，控制<code>map</code>数量需要遵循两个原则</p><ol><li>第一是使大数据量利用合适的<code>map</code>数</li><li>第二是使单个<code>map</code>任务处理合适的数据量</li></ol><h4 id="5.1.3-%E5%A4%8D%E6%9D%82%E6%96%87%E4%BB%B6%E9%80%82%E5%BD%93%E5%A2%9E%E5%8A%A0map%E6%95%B0" tabindex="-1">5.1.3 复杂文件适当增加Map数</h4><ol><li>当<code>input</code>的文件都很大，任务逻辑复杂，<code>map</code>执行非常慢的时候，可以考虑增加<code>map</code>数，来使得每个<code>map</code>处理的数据量减少，从而提高任务的执行效率</li><li>那么如何增加<code>map</code>的数量呢？在<code>map</code>阶段，文件先被切分成<code>split</code>块，而后每一个<code>split</code>切片对应一个<code>Mapper任务</code>，<br /><code>FileInputFormat</code>这个类先对输入文件进行逻辑上的划分，以<code>128m</code>为单位，将原始数据从逻辑上分割成若干个<code>split</code>，每个<code>split</code>切片对应一个<code>mapper任务</code>，<br />所以说减少切片的大小就可增加<code>map</code>数量</li><li>可以依据公式计算<code>computeSliteSize(Math.max(minSize, Math.min(maxSize, blockSize))) = blockSize = 128m</code></li><li>执行语句：<code>set mapreduce.input.fileinputformat.split.maxsize = 100;</code></li></ol><h4 id="5.1.4-%E5%B0%8F%E6%96%87%E4%BB%B6%E8%BF%9B%E8%A1%8C%E5%90%88%E5%B9%B6%E5%87%8F%E5%B0%91map%E6%95%B0" tabindex="-1">5.1.4 小文件进行合并减少Map数</h4><p>为什么要进行小文件合并？因为如果一个任务有很多小文件（远远小于块大小128m），则每个小文件也会被当做一个块，用一个<code>map</code>任务来完成，<br />而一个<code>map</code>任务启动和初始化的时间远远大于逻辑处理的时间，就会造成很大的资源浪费，同时可执行的<code>map</code>数是受限的<br /><em>两种方式合并小文件</em></p><ol><li>在<code>Map执行前</code>合并小文件，减少<code>map</code>数量<pre><code class="language-">// 每个Map最大输入大小(这个值决定了合并后文件的数量)set mapred.max.split.size = 256000000;// 一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)set mapred.min.split.size.per.node = 100000000;// 一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)set mapred.min.split.size.per.rack = 100000000;// 执行Map前进行小文件合并set hive.input.format = org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;</code></pre></li><li>在<code>Map-Reduce任务执行结束时</code>合并小文件，减少小文件输出<pre><code class="language-">// 设置map端输出进行合并，默认为trueset hive.merge.mapfiles = true;// 设置reduce端输出进行合并，默认为falseset hive.merge.mapredfiles = true;// 设置合并文件的大小，默认是256set hive.merge.size.per.task = 256 * 1000 * 1000;// 当输出文件的平均大小小于该值时，启动一个独立的&#96;MapReduce任务&#96;进行文件&#96;merge&#96;。set hive.merge.smallfiles.avgsize = 16000000;</code></pre></li></ol><h4 id="5.1.5-map%E7%AB%AF%E9%A2%84%E8%81%9A%E5%90%88%E5%87%8F%E5%B0%91map%E6%95%B0%E9%87%8F" tabindex="-1">5.1.5 Map端预聚合减少Map数量</h4><ol><li>相当于在<code>map</code>端执行<code>combiner</code>，执行命令：<code>set hive.map.aggr = true;</code></li><li><code>combiners</code>是对<code>map</code>端的数据进行适当的聚合，其好处是减少了从<code>map</code>端到<code>reduce</code>端的数据传输量</li><li>其作用的本质，是将<code>map</code>计算的结果进行二次聚合，使<code>Key-Value&lt;List&gt;</code>中<code>List</code>的数据量变小，从而达到减少数据量的目的</li></ol><h4 id="5.1.6-%E6%8E%A8%E6%B5%8B%E6%89%A7%E8%A1%8C" tabindex="-1">5.1.6 推测执行</h4><ol><li>在分布式集群环境下，因为程序Bug（包括Hadoop本身的bug），负载不均衡或者资源分布不均等原因，会造成同一个作业的多个任务之间运行速度不一致，<br />有些任务的运行速度可能明显慢于其他任务（比如一个作业的某个任务进度只有50%，而其他所有任务已经运行完毕），则这些任务会拖慢作业的整体执行进度</li><li>Hadoop采用了<code>推测执行（Speculative Execution）</code>机制，它根据一定的法则推测出拖后腿的任务，并为这样的任务启动一个备份任务，<br />让该任务与原始任务同时处理同一份数据，并最终选用最先成功运行完成任务的计算结果作为最终结果</li><li>执行命令：<code>set mapred.reduce.tasks.speculative.execution = true;  # 默认是true</code></li><li>当然，如果用户对于运行时的偏差非常敏感的话，那么可以将这些功能关闭掉，如果用户因为输入数据量很大而需要执行长时间的<code>map task</code>或者<code>reduce task</code>的话，<br />那么启动推测执行造成的浪费是非常巨大的</li></ol><h4 id="5.1.7-%E5%90%88%E7%90%86%E6%8E%A7%E5%88%B6map%E6%95%B0%E9%87%8F%E7%9A%84%E5%AE%9E%E9%99%85%E6%A1%88%E4%BE%8B" tabindex="-1">5.1.7 合理控制Map数量的实际案例</h4><p><strong>假设一个SQL任务：</strong></p><pre><code class="language-sql">SELECT COUNT(1) FROM fx67ll_alarm_count_copyWHERE alarm_date = &quot;2021-01-08&quot;;</code></pre><p>该任务的输入目录<code>inputdir</code>是：<code>/group/fx67ll_data/fx67ll_data_etl/date/fx67ll_alarm_count_copy/alarm_date=2021-01-08</code>，共有194个文件，<br />其中很多是远远小于<code>128m</code>的小文件，总大小约<code>9G</code>，正常执行会用<code>194个Map任务</code>，<code>map</code>总共消耗的计算资源：<code>SLOTS_MILLIS_MAPS= 610,023</code><br /><em>通过在Map执行前合并小文件，减少Map数</em></p><pre><code class="language-"># 前面三个参数确定合并文件块的大小# 大于文件块大小128m的，按照128m来分隔 # 小于128m,大于100m的，按照100m来分隔# 把那些小于100m的（包括小文件和分隔大文件剩下的），进行合并，最终生成了74个块set mapred.max.split.size=100000000;set mapred.min.split.size.per.node=100000000;set mapred.min.split.size.per.rack=100000000;set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;</code></pre><p>合并后，用了74个<code>map</code>任务，<code>map</code>消耗的计算资源：<code>SLOTS_MILLIS_MAPS= 323,098</code>，对于这个简单SQL任务，执行时间上可能差不多，但节省了一半的计算资源</p><p><strong>再假设这样一个SQL任务：</strong></p><pre><code class="language-sql">SELECT data_fx67ll,COUNT(1),COUNT(DISTINCT id),SUM(CASE WHEN …),SUM(CASE WHEN …),SUM(…)FROM fx67ll_device_info_zsGROUP data_fx67ll</code></pre><p>如果表<code>fx67ll_device_info_zs</code>只有一个文件，大小为<code>120m</code>，但包含几千万的记录，如果用1个<code>map</code>去完成这个任务，肯定是比较耗时的，<br />这种情况下，我们要考虑将这一个文件合理的拆分成多个<br /><em>增加Reduce数量，来增加Map数量</em></p><pre><code class="language-sql">set mapred.reduce.tasks=10;CREATE TABLE fx67ll_device_info_zs_tempASSELECT * FROM fx67ll_device_info_zsDISTRIBUTE BY RAND(123);</code></pre><p>这样会将<code>fx67ll_device_info_zs</code>表的记录，随机的分散到包含10个文件的<code>fx67ll_device_info_zs_temp</code>表中，<br />再用<code>fx67ll_device_info_zs_temp</code>代替上面<code>sql</code>中的<code>fx67ll_device_info_zs</code>表，<br />则会用10个<code>map</code>任务去完成，每个<code>map</code>任务处理大于<code>12m（几百万记录）</code>的数据，效率肯定会好很多</p><h3 id="5.2-hive-reduce-%E4%BC%98%E5%8C%96" tabindex="-1">5.2 Hive Reduce 优化</h3><h4 id="5.2.1-reduce%E6%95%B0%E9%87%8F%E5%A4%9A%E5%B0%91%E7%9A%84%E5%BD%B1%E5%93%8D" tabindex="-1">5.2.1 Reduce数量多少的影响</h4><ol><li>同<code>map</code>一样，启动和初始化<code>reduce</code>也会消耗时间和资源</li><li>另外，有多少个<code>reduce</code>，就会有多少个输出文件，如果生成了很多个小文件，那么如果这些小文件作为下一个任务的输入，则也会出现小文件过多的问题</li></ol><h4 id="5.2.2-%E6%8E%A7%E5%88%B6reduce%E6%95%B0%E7%9A%84%E5%8E%9F%E5%88%99" tabindex="-1">5.2.2 控制Reduce数的原则</h4><p>和<code>map</code>一样，控制<code>reduce</code>数量需要遵循两个原则</p><ol><li>第一是使大数据量利用合适的<code>reduce</code>数</li><li>第二是使单个<code>reduce</code>任务处理合适的数据量</li></ol><h4 id="5.2.3-hive%E8%87%AA%E5%B7%B1%E5%A6%82%E4%BD%95%E7%A1%AE%E5%AE%9Areduce%E6%95%B0" tabindex="-1">5.2.3 Hive自己如何确定Reduce数</h4><p><code>reduce</code>个数的设定极大影响任务执行效率，不指定<code>reduce</code>个数的情况下，Hive会猜测确定一个<code>reduce</code>个数，基于以下两个设定：</p><pre><code class="language-"># 每个reduce任务处理的数据量，默认为 1000^3=1Ghive.exec.reducers.bytes.per.reducer# 每个任务最大的reduce数，默认为999hive.exec.reducers.max</code></pre><p>计算<code>reducer</code>数的公式很简单<code>N = min(参数2，总输入数据量 / 参数1)</code><br />即，如果<code>reduce</code>的输入（<code>map</code>的输出）总大小不超过1G，那么只会有一个<code>reduce</code>任务</p><p>举个例子：</p><pre><code class="language-sql">SELECT alarm_date,COUNT(1) FROM fx67ll_alarm_count_copyWHERE alarm_date = &quot;2021-01-08&quot;GROUP BY alarm_date;</code></pre><p>该任务的输入目录<code>inputdir</code>是：<code>/group/fx67ll_data/fx67ll_data_etl/date/fx67ll_alarm_count_copy/alarm_date=2021-01-08</code>，<br />总大小为<code>9G</code>多，因此这句有10个<code>reduce</code></p><h4 id="5.2.4-%E5%A6%82%E4%BD%95%E8%B0%83%E6%95%B4reduce%E6%95%B0%E9%87%8F" tabindex="-1">5.2.4 如何调整Reduce数量</h4><p><em>注意！！！实际开发中，reduce的个数一般通过程序自动推定，而不人为干涉，因为人为控制的话，如果使用不当很容易造成结果不准确，且降低执行效率</em></p><ol><li>通过调整每个<code>reduce</code>任务处理的数据量来调整<code>reduce</code>个数，处理的数据量少了，任务数就多了<pre><code class="language-sql"># 设置每个reduce任务处理的数据量500M，默认是1Gset hive.exec.reducers.bytes.per.reducer = 500000000;SELECT alarm_date,COUNT(1) FROM fx67ll_alarm_count_copyWHERE alarm_date = &quot;2021-01-08&quot;GROUP BY alarm_date;这次有20个reduce</code></pre></li><li>直接调整每个<code>Job</code>中的最大<code>reduce</code>数量，过于简单粗暴，慎用，尽量不要，虽然设置了<code>reduce</code>的个数看起来好像执行速度变快了，但是实际并不是这样的<pre><code class="language-sql"># 设置每个任务最大的reduce数为15个，默认为999set mapred.reduce.tasks = 15;SELECT alarm_date,COUNT(1) FROM fx67ll_alarm_count_copyWHERE alarm_date = &quot;2021-01-08&quot;GROUP BY alarm_date;这次有15个reduce</code></pre></li></ol><h4 id="5.2.5-%E6%8E%A8%E6%B5%8B%E6%89%A7%E8%A1%8C" tabindex="-1">5.2.5 推测执行</h4><p><em>参考map优化的最后一项</em></p><h4 id="5.2.6-%E4%BB%80%E4%B9%88%E6%83%85%E5%86%B5%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AAreduce" tabindex="-1">5.2.6 什么情况下只有一个Reduce</h4><p>很多时候你会发现任务中不管数据量多大，不管你有没有设置调整<code>reduce</code>个数的参数，任务中一直都只有一个<code>reduce</code>任务，<br />其实只有一个<code>reduce</code>任务的情况，除了数据量小于<code>hive.exec.reducers.bytes.per.reducer</code>参数值的情况外，还有以下原因：</p><ol><li>没有<code>Group By</code>的汇总，例如：<pre><code class="language-sql">SELECT alarm_date,COUNT(1) FROM fx67ll_alarm_count_copyWHERE alarm_date = &quot;2021-01-08&quot;GROUP BY alarm_date;写成SELECT COUNT(1) FROM fx67ll_alarm_count_copyWHERE alarm_date = &quot;2021-01-08&quot;;注意避免这样情况的发生</code></pre></li><li>用了<code>Order by</code>排序，因为它会对数据进行全局排序，所以数据量特别大的时候效率非常低，尽量避免</li><li>有笛卡尔积，生产环境必须严格避免</li></ol><h3 id="5.3-hive-%E4%BB%BB%E5%8A%A1%E6%95%B4%E4%BD%93%E4%BC%98%E5%8C%96" tabindex="-1">5.3 Hive 任务整体优化</h3><h4 id="5.3.1-fetch%E6%8A%93%E5%8F%96" tabindex="-1">5.3.1 Fetch抓取</h4><p><code>Fetch抓取</code>是指Hive在某些情况的查询可以不必使用<code>mr 任务</code>，例如在执行一个简单的<code>select * from XX</code>时，我们只需要简单的进行抓取对应目录下的数据即可。<br />在<code>hive-default.xml.template</code>中，<code>hive.fetch.task.conversion（默认是morn）</code>，老版本中默认是<code>minimal</code>。<br />该属性为<code>morn</code>时，在全局查找，字段查找，limit查找等都不走<code>mr 任务</code></p><h4 id="5.3.2-%E6%9C%AC%E5%9C%B0%E6%A8%A1%E5%BC%8F" tabindex="-1">5.3.2 本地模式</h4><p>Hive也可以不将任务提交到集群进行运算，而是直接在一台节点上处理，因为消除了提交到集群的overhead，所以比较适合数据量很小，且逻辑不复杂的任务。<br />设置<code>hive.exec.mode.local.auto</code>为true可以开启本地模式，但任务的输入数据总量必须小于<code>hive.exec.mode.local.auto.inputbytes.max（默认值128MB）</code>，<br />且mapper数必须小于<code>hive.exec.mode.local.auto.tasks.max（默认值4）</code>，<code>reducer</code>数必须为<code>0或1</code>，才会真正用本地模式执行</p><h4 id="5.3.3-%E5%B9%B6%E8%A1%8C%E6%89%A7%E8%A1%8C" tabindex="-1">5.3.3 并行执行</h4><p>Hive中互相没有依赖关系的<code>job</code>间是可以并行执行的，最典型的就是多个子查询<code>union all</code>，<br />在集群资源相对充足的情况下，可以开启并行执行，即将参数<code>hive.exec.parallel</code>设为true，<br />另外<code>hive.exec.parallel.thread.number</code>可以设定并行执行的线程数，默认为8，一般都够用。<br /><em>注意！！！没资源无法并行，且数据量小时开启可能还没不开启快，所以建议数据量大时开启</em></p><h4 id="5.3.4-%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F" tabindex="-1">5.3.4 严格模式</h4><p>要开启严格模式，需要将参数<code>hive.mapred.mode设为strict</code>。<br />所谓严格模式，就是强制不允许用户执行3种有风险的<code>sql</code>语句，一旦执行会直接失败，这3种语句是：</p><ol><li>查询分区表时不限定分区列的语句</li><li>两表join产生了笛卡尔积的语句</li><li>用order by来排序但没有指定limit的语句</li></ol><h4 id="5.3.5-jvm%E9%87%8D%E7%94%A8" tabindex="-1">5.3.5 JVM重用</h4><ol><li>主要用于处理小文件过多的时候</li><li>在<code>mr 任务</code>中，默认是每执行一个<code>task</code>就启动一个<code>JVM</code>，如果<code>task</code>非常小而碎，那么<code>JVM</code>启动和关闭的耗时就会很长</li><li>可以通过调节参数<code>mapred.job.reuse.jvm.num.tasks</code>来重用</li><li>例如将这个参数设成5，那么就代表同一个<code>mr 任务</code>中顺序执行的5个<code>task</code>可以重复使用一个<code>JVM</code>，减少启动和关闭的开销，但它对不同<code>mr 任务</code>中的<code>task</code>无效</li></ol><h4 id="5.3.6-%E5%90%AF%E7%94%A8%E5%8E%8B%E7%BC%A9" tabindex="-1">5.3.6 启用压缩</h4><p>压缩job的中间结果数据和输出数据，可以用少量CPU时间节省很多空间，压缩方式一般选择Snappy，效率最高。<br />要启用中间压缩，需要设定<code>hive.exec.compress.intermediate</code>为true，<br />同时指定压缩方式<code>hive.intermediate.compression.codec</code>为<code>org.apache.hadoop.io.compress.SnappyCodec</code>。<br />另外，参数<code>hive.intermediate.compression.type</code>可以选择对<code>块（BLOCK）</code>还是<code>记录（RECORD）</code>压缩，BLOCK的压缩率比较高。<br />输出压缩的配置基本相同，打开<code>hive.exec.compress.output</code>即可</p><h4 id="5.3.7-%E9%87%87%E7%94%A8%E5%90%88%E9%80%82%E7%9A%84%E5%AD%98%E5%82%A8%E6%A0%BC%E5%BC%8F" tabindex="-1">5.3.7 采用合适的存储格式</h4><ol><li>在Hive SQL的<code>create table</code>语句中，可以使用<code>stored as ...</code>指定表的存储格式。<br />Hive表支持的存储格式有<code>TextFile</code>、<code>SequenceFile</code>、<code>RCFile</code>、<code>Avro</code>、<code>ORC</code>、<code>Parquet</code>等。<br />存储格式一般需要根据业务进行选择，在我们的实操中，绝大多数表都采用<code>TextFile</code>与<code>Parquet</code>两种存储格式之一。</li><li><code>TextFile</code>是最简单的存储格式，它是纯文本记录，也是Hive的默认格式，虽然它的磁盘开销比较大，查询效率也低，但它更多地是作为跳板来使用。</li><li><code>RCFile</code>、<code>ORC</code>、<code>Parquet</code>等格式的表都不能由文件直接导入数据，必须由<code>TextFile</code>来做中转。</li><li><code>Parquet</code>和<code>ORC</code>都是Apache旗下的开源列式存储格式。列式存储比起传统的行式存储更适合<code>批量OLAP查询</code>，并且也支持更好的压缩和编码。</li><li>我们选择<code>Parquet</code>的原因主要是它支持<code>Impala查询引擎</code>，并且我们对<code>update</code>、<code>delete</code>和<code>事务性操作</code>需求很低。</li></ol><h2 id="%E5%85%AD%E3%80%81hive%E7%9A%84%E5%B0%8F%E6%96%87%E4%BB%B6" tabindex="-1">六、Hive的小文件</h2><h3 id="6.1-%E4%BB%80%E4%B9%88%E6%83%85%E5%86%B5%E4%B8%8B%E4%BC%9A%E4%BA%A7%E7%94%9F%E5%B0%8F%E6%96%87%E4%BB%B6%3F" tabindex="-1">6.1 什么情况下会产生小文件?</h3><ol><li>动态分区插入数据，产生大量的小文件，从而导致map数量剧增</li><li>reduce数量越多，小文件也越多，有多少个reduce，就会有多少个输出文件，如果生成了很多小文件，那这些小文件作为下一次任务的输入</li><li>数据源本身就包含大量的小文件</li></ol><h3 id="6.2-%E5%B0%8F%E6%96%87%E4%BB%B6%E6%9C%89%E4%BB%80%E4%B9%88%E6%A0%B7%E7%9A%84%E5%8D%B1%E5%AE%B3%EF%BC%9F" tabindex="-1">6.2 小文件有什么样的危害？</h3><ol><li>从Hive的角度看，小文件会开很多map，一个map开一个java虚拟机jvm去执行，所以这些任务的初始化，启动，执行会浪费大量的资源，严重影响性能</li><li>在hdfs中，每个小文件对象约占150byte，如果小文件过多会占用大量内存，这样NameNode内存容量严重制约了集群的扩展<ul><li>每个hdfs上的文件，会消耗128字节记录其meta信息，所以大量小文件会占用大量内存</li></ul></li></ol><h3 id="6.3-%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E5%B0%8F%E6%96%87%E4%BB%B6%E5%B8%A6%E6%9D%A5%E7%9A%84%E5%8D%B1%E5%AE%B3%EF%BC%9F" tabindex="-1">6.3 如何避免小文件带来的危害？</h3><h4 id="6.3.1-%E4%BB%8E%E5%B0%8F%E6%96%87%E4%BB%B6%E4%BA%A7%E7%94%9F%E7%9A%84%E9%80%94%E7%BB%8F%E5%B0%B1%E5%8F%AF%E4%BB%A5%E4%BB%8E%E6%BA%90%E5%A4%B4%E4%B8%8A%E6%8E%A7%E5%88%B6%E5%B0%8F%E6%96%87%E4%BB%B6%E6%95%B0%E9%87%8F" tabindex="-1">6.3.1 从小文件产生的途经就可以从源头上控制小文件数量</h4><ol><li>使用Sequencefile作为表存储格式，不要用textfile，在一定程度上可以减少小文件</li><li>减少reduce的数量(可以使用参数进行控制)</li><li>少用动态分区，用时记得按distribute by分区</li></ol><h4 id="6.3.2-%E5%AF%B9%E4%BA%8E%E5%B7%B2%E6%9C%89%E7%9A%84%E5%B0%8F%E6%96%87%E4%BB%B6" tabindex="-1">6.3.2 对于已有的小文件</h4><ol><li>使用hadoop archive命令把小文件进行归档，采用archive命令不会减少文件存储大小，只会压缩NameNode的空间使用</li><li>重建表，建表时减少reduce数量</li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llBigData/blob/main/note/hive/hive-optimize-use.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[HiveSQL工作实战总结]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/hive-sql-inwork" />
                <id>tag:https://fx67ll.xyz,2022-05-01:hive-sql-inwork</id>
                <published>2022-05-01T16:06:05+08:00</published>
                <updated>2025-07-03T10:42:10+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>记录一些工作中有意思的统计指标，做过一些简化方便大家阅读，记录如有错误，欢迎在评论区提问讨论~</p><h2 id="%E9%97%AE%E9%A2%98%E7%B1%BB%E5%9E%8B" tabindex="-1">问题类型</h2><ol><li>连续问题<ul><li>两种思路</li><li>第一种：日期减去一列数字得出日期相同，主要是通过<code>row_number</code>窗口函数</li><li>第二种：后一个日期减去前一个日期差值相等，用的较少，可以用<code>lag/lead</code>窗口函数解决</li></ul></li><li>分组问题<ul><li>主要使用<code>lag(col,1,0)</code>分组将每行移到下一行，再按既定规则分组排序即可</li><li><a href="https://www.bilibili.com/video/BV1Y34y1C7mV?p=34" target="_blank">后面抽空试一下</a></li></ul></li><li>间隔连续问题，比如每断一天也算连续<ul><li>两种思路：</li><li>第一种：连续使用两次连续问题的求法即可，差了几次可以连续<code>row_number</code>几次，<em>这种无限套娃不推荐使用</em></li><li>第二种：连续差值小于要求数即可，比如断一天也可，只要每行间隔小于2天即可</li></ul></li><li>打折日期交叉问题，两段活动期重复日期去除</li><li>同时在线问题</li></ol><h2 id="%E4%B8%80%E3%80%81%E7%BB%9F%E8%AE%A1%E6%AF%8F%E4%B8%AA%E8%AE%BE%E5%A4%87%E7%9A%84%E7%B4%AF%E8%AE%A1%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0" tabindex="-1">一、统计每个设备的累计告警次数</h2><h3 id="%E5%8E%9F%E5%A7%8B%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F" tabindex="-1">原始数据格式</h3><table><thead><tr><th style="text-align:center">deviceId</th><th style="text-align:center">alarmDate</th><th style="text-align:center">alarmCount</th></tr></thead><tbody><tr><td style="text-align:center">设备ID</td><td style="text-align:center">告警日期</td><td style="text-align:center">告警次数</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/8</td><td style="text-align:center">5</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022/1/8</td><td style="text-align:center">7</td></tr><tr><td style="text-align:center">u03</td><td style="text-align:center">2022/1/8</td><td style="text-align:center">3</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/12</td><td style="text-align:center">2</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022/1/12</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/14</td><td style="text-align:center">9</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h3 id="%E7%BB%9F%E8%AE%A1%E4%B9%8B%E5%90%8E%E6%A0%BC%E5%BC%8F" tabindex="-1">统计之后格式</h3><table><thead><tr><th style="text-align:center">设备ID</th><th style="text-align:center">告警月份</th><th style="text-align:center">告警次数小计</th><th style="text-align:center">告警次数累计</th></tr></thead><tbody><tr><td style="text-align:center">u01</td><td style="text-align:center">2022-02</td><td style="text-align:center">11</td><td style="text-align:center">11</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022-03</td><td style="text-align:center">12</td><td style="text-align:center">23</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h3 id="%E5%B7%A5%E4%BD%9C%E6%80%9D%E8%B7%AF" tabindex="-1">工作思路</h3><ol><li>先根据设备ID和告警日期分组</li><li>按月份统计可以用substr函数或者日期格式化函数</li><li>再统计即可得出小计告警次数</li><li>接着使用聚合窗口函数计算累计告警次数</li></ol><h3 id="%E5%B7%A5%E4%BD%9C%E8%AF%AD%E5%8F%A5" tabindex="-1">工作语句</h3><ol><li>第一种方案，使用<code>substr</code>截取字符串函数</li></ol><pre><code class="language-sql">SELECT *,   SUM(sumPart) OVER (PARTITION BY deviceId ORDER BY alarmMonth) AS sumAllFROM(SELECT deviceId,   SUBSTR(alarmDate,1,7) AS alarmMonth,   SUM(alarmCount) AS sumPartFROM test_00GROUP BY deviceId, alarmMonth) t;</code></pre><ol start="2"><li>第二种方案，使用日期格式化函数，使用<code>date_format</code>函数的字符串必须满足yyyy-MM-dd格式，所以必须先用<code>regexp_replace</code>替换<code>/</code>为<code>-</code></li></ol><pre><code class="language-sql">SELECT *,   SUM(sumPart) OVER (PARTITION BY deviceId ORDER BY alarmMonth) AS sumAllFROM(SELECT deviceId,    DATE_FORMAT(REGEXP_REPLACE(alarmDate,&#39;/&#39;,&#39;-&#39;), &#39;yyyy-MM&#39;) AS alarmMonth,    SUM(alarmCount) AS sumPartFROM test_00GROUP BY deviceId, alarmMonth) t;</code></pre><h4 id="%E5%BC%80%E7%AA%97%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84%E7%95%8C%E9%99%90%E8%AF%B4%E6%98%8E" tabindex="-1">开窗函数中的界限说明</h4><ul><li>unbounded：无界限</li><li>preceding：从分区第一行头开始，则为 <code>unbounded</code> <em>N为：相对当前行向后的偏移量</em></li><li>following ：与<code>preceding</code>相反,到该分区结束，则为 <code>unbounded</code> <em>N为：相对当前行向后的偏移量</em></li><li>current row：顾名思义，当前行，偏移量为0</li></ul><h2 id="%E4%BA%8C%E3%80%81%E7%BB%9F%E8%AE%A1%E7%8E%AF%E5%A2%83%E8%AE%BE%E5%A4%87%E6%AF%8F%E5%A4%A9%E7%9A%84%E6%80%BB%E6%B1%A1%E6%9F%93%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0%EF%BC%8C%E5%B9%B6%E8%BE%93%E5%87%BA%E6%AF%8F%E4%B8%AA%E8%AE%BE%E5%A4%87%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0%E6%8E%92%E5%90%8D%E5%89%8D%E4%B8%89%E7%9A%84%E6%97%A5%E6%9C%9F" tabindex="-1">二、统计环境设备每天的总污染告警次数，并输出每个设备告警次数排名前三的日期</h2><h3 id="%E5%8E%9F%E5%A7%8B%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F-1" tabindex="-1">原始数据格式</h3><table><thead><tr><th style="text-align:center">deviceId</th><th style="text-align:center">alarmTime</th></tr></thead><tbody><tr><td style="text-align:center">设备ID</td><td style="text-align:center">告警时间</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/8/08/04/58</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022/1/8/12/05/38</td></tr><tr><td style="text-align:center">u03</td><td style="text-align:center">2022/1/8/17/01/12</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/12/12/04/53</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022/1/12/13/45/34</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/14/02/12/51</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h3 id="%E7%BB%9F%E8%AE%A1%E4%B9%8B%E5%90%8E%E6%A0%BC%E5%BC%8F-1" tabindex="-1">统计之后格式</h3><table><thead><tr><th style="text-align:center">设备ID</th><th style="text-align:center">告警次数累计</th></tr></thead><tbody><tr><td style="text-align:center">u01</td><td style="text-align:center">3</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2</td></tr><tr><td style="text-align:center">u03</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h3 id="%E5%B7%A5%E4%BD%9C%E6%80%9D%E8%B7%AF-1" tabindex="-1">工作思路</h3><h4 id="%E7%BB%9F%E8%AE%A1%E7%8E%AF%E5%A2%83%E8%AE%BE%E5%A4%87%E7%9A%84%E6%80%BB%E6%B1%A1%E6%9F%93%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0" tabindex="-1">统计环境设备的总污染告警次数</h4><ol><li>由于有设备可能会有同一时间的告警记录，所以需要按告警时间去重后再统计</li><li>如果使用distinct去重，如果表数据过大，且设备ID差异化很大，那么会有性能压力</li><li>所以使用group by子查询代替</li><li>mysql中的<code>date_format</code>格式化需要这样写：<code>DATE_FORMAT(alarmTime, '%Y-%c-%d %T')</code></li></ol><h4 id="%E8%BE%93%E5%87%BA%E6%AF%8F%E4%B8%AA%E8%AE%BE%E5%A4%87%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0%E6%8E%92%E5%90%8D%E5%89%8D%E4%B8%89%E7%9A%84%E6%97%A5%E6%9C%9F" tabindex="-1">输出每个设备告警次数排名前三的日期</h4><ol><li>使用窗口函数<code>ROW_NUMBER() OVER()</code>进行分组排序即可，<a href="https://blog.csdn.net/liuyaru11/article/details/106685366" target="_blank">MySQL 替换 ROW_NUMBER() OVER (PARTITION ……) 函数</a></li><li>多个子句查询可以使用视图和<code>WITH</code>语句</li></ol><h3 id="%E5%B7%A5%E4%BD%9C%E8%AF%AD%E5%8F%A5-1" tabindex="-1">工作语句</h3><h4 id="%E7%BB%9F%E8%AE%A1%E7%8E%AF%E5%A2%83%E8%AE%BE%E5%A4%87%E7%9A%84%E6%80%BB%E6%B1%A1%E6%9F%93%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0-1" tabindex="-1">统计环境设备的总污染告警次数</h4><pre><code class="language-sql">SELECT deviceId,COUNT(alarmTime) AS alarmCount FROM--- http://c.biancheng.net/mysql/date_format.html(SELECT deviceId,DATE_FORMAT(REGEXP_REPLACE(alarmTime,&#39;/&#39;,&#39;-&#39;), &#39;yyyy-MM-dd HH:mm:ss&#39;) AS alarmTimeFROM test_01GROUP BY deviceId,alarmTimeORDER BY alarmTime) tGROUP BY deviceId;</code></pre><h4 id="%E8%BE%93%E5%87%BA%E6%AF%8F%E4%B8%AA%E8%AE%BE%E5%A4%87%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0%E6%8E%92%E5%90%8D%E5%89%8D%E4%B8%89%E7%9A%84%E6%97%A5%E6%9C%9F-1" tabindex="-1">输出每个设备告警次数排名前三的日期</h4><pre><code class="language-sql">SELECT * FROM (SELECT deviceId,alarmDate,alarmCount,ROW_NUMBER() OVER(PARTITION BY deviceId ORDER BY alarmCount DESC) AS alarmRankFROM (SELECT deviceId,alarmDate,COUNT(alarmDate) AS alarmCountFROM(SELECT deviceId,DATE_FORMAT(alarmTime, &#39;yyyy-MM-dd&#39;) AS alarmDate,DATE_FORMAT(alarmTime, &#39;yyyy-MM-dd HH:mm:ss&#39;) AS alarmTimeFROM test_01GROUP BY deviceId,alarmTimeORDER BY deviceId,alarmTime) t1GROUP BY deviceId,alarmDate) t2) t3WHERE alarmRank&lt;=3;-- 使用WITH语句优化一下WITH t1 AS (SELECT deviceId,DATE_FORMAT(alarmTime, &#39;yyyy-MM-dd&#39;) AS alarmDate,DATE_FORMAT(alarmTime, &#39;yyyy-MM-dd HH:mm:ss&#39;) AS alarmTimeFROM test_01GROUP BY deviceId,alarmTimeORDER BY deviceId,alarmTime),t2 AS (SELECT deviceId,alarmDate,COUNT(alarmDate) AS alarmCountFROM t1GROUP BY deviceId,alarmDate),t3 AS (SELECT deviceId,alarmDate,alarmCount,ROW_NUMBER() OVER(PARTITION BY deviceId ORDER BY alarmCount DESC) AS alarmRankFROM t2)SELECT * FROM t3 WHERE alarmRank&lt;=3;</code></pre><h4 id="count(1)%E5%92%8Ccount(*)%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">COUNT(1)和COUNT(*)的区别</h4><ol><li>从执行结果来说<ul><li><code>COUNT(1)</code>和<code>COUNT(*)</code>之间没有区别，因为<code>COUNT(*)</code>和<code>COUNT(1)</code>都不会去过滤空值</li><li>但<code>COUNT(列名)</code>就有区别了，因为<code>COUNT(列名)</code>会去过滤空值</li></ul></li><li>从执行效率来说<ul><li>他们之间根据不同情况会有些许区别，MySQL会对<code>COUNT(*)</code>做优化</li><li>如果列为主键，<code>COUNT(列名)</code>效率优于<code>COUNT(1)</code></li><li>如果列不为主键，<code>COUNT(1)</code>效率优于<code>COUNT(列名)</code></li><li>如果表中存在主键，<code>COUNT(主键列名)</code>效率最优</li><li>如果表中只有一列，则<code>COUNT(*)</code>效率最优</li><li>如果表有多列，且不存在主键，则<code>COUNT(1)</code>效率优于<code>COUNT(*)</code></li></ul></li></ol><h2 id="%E4%B8%89%E3%80%81%E7%BB%9F%E8%AE%A1%E6%AF%8F%E4%B8%AA%E6%9C%88%E7%9A%84%E6%80%BB%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0%EF%BC%8C%E6%80%BB%E5%91%8A%E8%AD%A6%E8%AE%BE%E5%A4%87%E6%95%B0%EF%BC%8C%E4%BB%A5%E5%8F%8A%E8%83%BD%E5%A4%9F%E8%BF%9E%E7%BB%AD%E4%B8%83%E5%A4%A9%E6%95%B0%E5%80%BC%E6%AD%A3%E5%B8%B8%E8%AE%BE%E5%A4%87%E6%95%B0%E9%87%8F" tabindex="-1">三、统计每个月的总告警次数，总告警设备数，以及能够连续七天数值正常设备数量</h2><p><strong>留给你思考</strong></p><h2 id="%E5%9B%9B%E3%80%81%E7%BB%9F%E8%AE%A1%E5%87%BA2022%E5%B9%B41%E6%9C%88%E9%A6%96%E6%AC%A1%E5%87%BA%E7%8E%B0%E5%91%8A%E8%AD%A6%E7%9A%84%E8%AE%BE%E5%A4%87%E6%95%B0%E9%87%8F" tabindex="-1">四、统计出2022年1月首次出现告警的设备数量</h2><h3 id="%E5%8E%9F%E5%A7%8B%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F-2" tabindex="-1">原始数据格式</h3><table><thead><tr><th style="text-align:center">deviceId</th><th style="text-align:center">alarmTime</th></tr></thead><tbody><tr><td style="text-align:center">设备ID</td><td style="text-align:center">告警时间</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/8/08/04/58</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022/2/8/12/05/38</td></tr><tr><td style="text-align:center">u03</td><td style="text-align:center">2021/9/8/17/01/12</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/12/12/04/53</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022/4/12/13/45/34</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/5/14/02/12/51</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h3 id="%E7%BB%9F%E8%AE%A1%E4%B9%8B%E5%90%8E%E6%A0%BC%E5%BC%8F-2" tabindex="-1">统计之后格式</h3><table><thead><tr><th style="text-align:center">设备ID</th><th style="text-align:center">首次告警时间</th></tr></thead><tbody><tr><td style="text-align:center">xxx</td><td style="text-align:center">2022/1/8/08/04/58</td></tr><tr><td style="text-align:center">xxx</td><td style="text-align:center">2022/1/8/12/05/38</td></tr><tr><td style="text-align:center">xxx</td><td style="text-align:center">2022/1/8/17/01/12</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h3 id="%E5%B7%A5%E4%BD%9C%E6%80%9D%E8%B7%AF-2" tabindex="-1">工作思路</h3><ol><li>先用<code>date_format</code>格式化所有设备告警时间为<code>yyyy-MM</code>的日期格式</li><li>运用<code>min</code>函数得出每个设备最早告警日期</li><li>当最早告警日期是<code>2022年1月</code>的时候即为我们所需要知道的设备记录</li></ol><h3 id="%E5%B7%A5%E4%BD%9C%E8%AF%AD%E5%8F%A5-2" tabindex="-1">工作语句</h3><pre><code class="language-sql">WITH t1 AS (SELECT *,DATE_FORMAT(alarmTime, &#39;yyyy-MM&#39;) AS alarmMonth FROM test_01),t2 AS (SELECT deviceId,alarmTime,MIN(alarmMonth) AS firstAlarmMonthFROM t1GROUP BY deviceId)SELECT * FROM t2 WHERE firstAlarmMonth=&#39;2022-1&#39;;</code></pre><h2 id="%E4%BA%94%E3%80%81%E6%A0%B9%E6%8D%AE%E8%AE%BE%E5%A4%87%E5%9C%B0%E5%8C%BA%E7%BC%96%E5%8F%B7%E6%AE%B5%E5%AF%B9%E5%91%8A%E8%AD%A6%E6%AC%A1%E6%95%B0%E8%BF%9B%E8%A1%8C%E6%8E%92%E5%BA%8F" tabindex="-1">五、根据设备地区编号段对告警次数进行排序</h2><p><strong>有一个5000万的设备信息表，一个2亿记录的告警记录表</strong></p><h3 id="%E5%8E%9F%E5%A7%8B%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F-3" tabindex="-1">原始数据格式</h3><ol><li>设备信息表</li></ol><table><thead><tr><th style="text-align:center">deviceId</th><th style="text-align:center">deviceName</th><th style="text-align:center">deviceDistrict</th></tr></thead><tbody><tr><td style="text-align:center">设备ID</td><td style="text-align:center">设备名称</td><td style="text-align:center">设备所属地区</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">xx01</td><td style="text-align:center">210000</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">xx02</td><td style="text-align:center">210010</td></tr><tr><td style="text-align:center">u03</td><td style="text-align:center">xx03</td><td style="text-align:center">210025</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><ol start="2"><li>告警记录表</li></ol><table><thead><tr><th style="text-align:center">deviceId</th><th style="text-align:center">alarmTime</th></tr></thead><tbody><tr><td style="text-align:center">设备ID</td><td style="text-align:center">告警时间</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/8/08/04/58</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022/2/8/12/05/38</td></tr><tr><td style="text-align:center">u03</td><td style="text-align:center">2021/9/8/17/01/12</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/1/12/12/04/53</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022/4/12/13/45/34</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022/5/14/02/12/51</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h3 id="%E7%BB%9F%E8%AE%A1%E4%B9%8B%E5%90%8E%E6%A0%BC%E5%BC%8F-3" tabindex="-1">统计之后格式</h3><table><thead><tr><th style="text-align:center">设备地区号段</th><th style="text-align:center">告警次数</th></tr></thead><tbody><tr><td style="text-align:center">210000-210010</td><td style="text-align:center">2</td></tr><tr><td style="text-align:center">210010-210020</td><td style="text-align:center">8</td></tr><tr><td style="text-align:center">210020-210030</td><td style="text-align:center">4</td></tr><tr><td style="text-align:center">210040-210050</td><td style="text-align:center">7</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h3 id="%E5%B7%A5%E4%BD%9C%E6%80%9D%E8%B7%AF-3" tabindex="-1">工作思路</h3><ol><li>先根据设备ID分组<code>count</code>统计报警次数</li><li>再使用casewhen条件语句，或者使用<code>concat/floor/ceil</code>函数动态划分，根据分段统计不同设备位于什么地区号段</li><li>最后连接查询，并根据地区号段，使用<code>sum</code>函数统计总告警次数即可</li></ol><h3 id="%E5%B7%A5%E4%BD%9C%E8%AF%AD%E5%8F%A5-3" tabindex="-1">工作语句</h3><pre><code class="language-sql">-- 第一种方案WITH t1 AS(SELECT deviceId,COUNT(alarmTime) AS alarmCountFROM test_01GROUP BY deviceId),t2 AS(SELECT deviceId,deviceDistrict,-- 如果地区编号是字符串可以先转换再比较，不然会触发隐式转换，导致全表扫描无法使用索引-- CONVERT(deviceDistrict, UNSIGNED)&gt;=210000CASE WHEN deviceDistrict&gt;=210000 AND deviceDistrict&lt;210010 THEN &#39;210000-210010&#39; WHEN deviceDistrict&gt;=210010 AND deviceDistrict&lt;210020 THEN &#39;210010-210020&#39;WHEN deviceDistrict&gt;=210020 AND deviceDistrict&lt;210030 THEN &#39;210020-210030&#39; WHEN deviceDistrict&gt;=210030 AND deviceDistrict&lt;210040 THEN &#39;210030-210040&#39; WHEN deviceDistrict&gt;=210040 AND deviceDistrict&lt;210050 THEN &#39;210040-210050&#39; WHEN deviceDistrict&gt;=210050 AND deviceDistrict&lt;210060 THEN &#39;210050-210060&#39; WHEN deviceDistrict&gt;=210060 AND deviceDistrict&lt;210070 THEN &#39;210060-210070&#39;END deviceDistrictSectionFROM test_02),t3 AS (SELECT t2.deviceDistrictSection AS deviceDistrictSection,SUM(t1.alarmCount) AS alarmCountFROM t1 LEFT JOIN t2 ON t1.deviceId = t2.deviceIdGROUP BY deviceDistrictSectionORDER BY deviceDistrictSection)SELECT * FROM t3;-- 第二种方案WITH t1 AS(SELECT deviceId,COUNT(alarmTime) AS alarmCountFROM test_01GROUP BY deviceId),t2 AS(SELECT deviceId,deviceDistrict,CONCAT(FLOOR(deviceDistrict/10)*10, &#39;-&#39;, (FLOOR(deviceDistrict/10)+1)*10) AS deviceDistrictSectionFROM test_02),t3 AS (SELECT t2.deviceDistrictSection AS deviceDistrictSection,SUM(t1.alarmCount) AS alarmCountFROM t1 LEFT JOIN t2 ON t1.deviceId = t2.deviceIdGROUP BY deviceDistrictSectionORDER BY deviceDistrictSection)SELECT * FROM t3;-- 第二种方案的函数测试SELECT FLOOR(210015/10)*10 AS x;  -- 210015SELECT CEIL(210015/10)*10 AS y;  -- 210020SELECT CONCAT(FLOOR(210015/10)*10, &#39;-&#39;, CEIL(210015/10)*10);  -- 210010-210020SELECT CONCAT(FLOOR(210020/10)*10, &#39;-&#39;, CEIL(210020/10)*10);  -- 210020-210020SELECT CONCAT(FLOOR(210020/10)*10, &#39;-&#39;, (FLOOR(210020/10)+1)*10);  -- 210020-210030</code></pre><h4 id="%E6%8B%BC%E6%8E%A5%E5%87%BD%E6%95%B0concat%2Fconcat_ws%2Fgroup_concat%E7%9A%84%E5%8C%BA%E5%88%AB%E8%AF%B4%E6%98%8E" tabindex="-1">拼接函数<code>concat</code>/<code>concat_ws</code>/<code>group_concat</code>的区别说明</h4><ol><li><code>concat</code><ul><li>将多个字符串连接成一个字符串</li><li><code>concat(str1, str2,...)</code></li><li>返回结果为连接参数产生的字符串，如果有任何一个参数为null，则返回值为null</li></ul></li><li><code>concat_ws</code><ul><li>和concat()一样，将多个字符串连接成一个字符串，但是可以一次性指定分隔符</li><li>第一个参数指定分隔符，<code>concat_ws(separator, str1, str2, ...)</code></li><li>返回结果为连接参数产生的字符串。需要注意的是分隔符不能为null，如果为null，则返回结果为null</li></ul></li><li><code>group_concat</code><ul><li>将<code>group by</code>产生的同一个分组中的值连接起来，返回一个字符串结果</li><li><code>group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc  ] [separator '分隔符'] )</code></li><li>*说明：*通过使用<code>distinct</code>可以排除重复值；如果希望对结果中的值进行排序，可以使用<code>order by</code>子句；<code>separator</code>是一个字符串值，缺省为一个逗号</li></ul></li></ol><h4 id="%E6%8B%BC%E6%8E%A5%E5%87%BD%E6%95%B0floor%2Fceil%2Fround%E7%9A%84%E5%8C%BA%E5%88%AB%E8%AF%B4%E6%98%8E" tabindex="-1">拼接函数<code>floor</code>/<code>ceil</code>/<code>round</code>的区别说明</h4><ol><li><code>floor</code><ul><li>在英文中，是地面，地板的意思，有下面的意思；所以此函数是向下取整，它返回的是小于或等于函数参数，并且与之最接近的整数</li><li>向下取整的时候，正数，则取其整数部位，抹除小数部位</li><li>负数，则取其整数加一</li><li>整数，则不变</li></ul></li><li><code>ceil</code><ul><li>在英文中，是天花板的意思，有向上的意思；所以此函数是向上取整，它返回的是大于或等于函数参数，并且与之最接近的整数</li><li>向上取整的时候，正数，则直接将当前整数加一</li><li>负数，则将整数后面的数据抹除</li><li>整数，则不变</li></ul></li><li><code>round</code><ul><li>在英文中是有大约，环绕，在某某四周，附近的意思，所以，可以取其大约的意思，在函数中是四舍五入</li><li>四舍五入的时候，正数，小数位大于5，则整数位加一，小数位小于5，则整数位不变，抹除小数位</li><li>负数，小数位小于5，则整数位不变，抹除小数位，小数位大于5，则整数位加一</li><li>整数，则不变</li></ul></li></ol><h4 id="mysql%E4%B8%AD%E4%BF%9D%E7%95%99%E4%B8%A4%E4%BD%8D%E5%B0%8F%E6%95%B0" tabindex="-1">MySQL中保留两位小数</h4><ol><li><code>round(x,d)</code> 四舍五入保留小数<ul><li><code>round(x)</code>其实就是<code>round(x,0)</code>，也就是默认d为0，默认不保留小数，d为保留几位小数</li><li>d可以是负数，这时是指定小数点左边的d位整数位为0，同时小数位均为0，例如：<code>round(114.6, -1) -&gt; 110</code></li></ul></li><li><code>truncate(x,d)</code> 函数返回被舍去至小数点后d位的数字x，和<code>round</code>函数类似，但是没有四舍五入</li><li><code>format(x,d)</code> 强制保留d位小数，整数部分超过三位的时候以逗号分割，并且返回的结果是<code>string</code>类型的</li><li><code>convert(value,type)</code> 类型转换，相当于截取，例如：<ul><li>convert(100.3465, decimal(10,2)) -&gt; 100.35</li><li>convert(100, decimal(10,2)) -&gt; 100</li><li>convert(100.4, decimal(10,2)) -&gt; 100.4</li></ul></li></ol><h4 id="hive%E4%B8%AD%E4%BF%9D%E7%95%99%E4%B8%A4%E4%BD%8D%E5%B0%8F%E6%95%B0" tabindex="-1">Hive中保留两位小数</h4><ol><li><code>round(column_name,2)</code> 四舍五入截取 <em>这种方法慎用，有时候结果不是你想要的</em></li><li><code>regexp_extract(column_name,'([0-9]*.[0-9][0-9])',1)</code> 正则匹配截取，不做四舍五入，只是单纯的当作字符串截取</li><li><code>cast(column_name as decimal(10,2))</code> cast函数截取 <em>推荐使用</em></li></ol><h2 id="%E5%85%AD%E3%80%81%E7%BB%9F%E8%AE%A1%E6%89%80%E6%9C%89%E5%91%8A%E8%AD%A6%E8%AE%BE%E5%A4%87%E5%92%8C%E6%89%80%E6%9C%89%E6%B4%BB%E8%B7%83%E5%91%8A%E8%AD%A6%E8%AE%BE%E5%A4%87%EF%BC%88%E8%BF%9E%E7%BB%AD%E4%B8%89%E5%A4%A9%E9%83%BD%E6%9C%89%E5%91%8A%E8%AD%A6%E7%9A%84%E8%AE%BE%E5%A4%87%EF%BC%89%E7%9A%84%E6%80%BB%E6%95%B0%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%B9%B3%E5%9D%87%E7%9B%91%E6%B5%8B%E5%80%BC" tabindex="-1">六、统计所有告警设备和所有活跃告警设备（连续三天都有告警的设备）的总数，以及平均监测值</h2><p><strong>活跃告警设备是指连续三天都有告警的设备</strong></p><h4 id="%E8%BF%9E%E7%BB%ADn%E5%A4%A9%E7%99%BB%E5%BD%95%E7%AD%89%E7%B1%BB%E4%BC%BC%E9%A2%98%E7%9B%AE%E7%9A%84%E8%A7%A3%E9%A2%98%E6%80%9D%E8%B7%AF" tabindex="-1">连续N天登录等类似题目的解题思路</h4><ol><li>日期减去一列数字得到的日期相等</li><li>后一个日期减去前一个日期的差值相等</li></ol><h3 id="%E5%8E%9F%E5%A7%8B%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F-4" tabindex="-1">原始数据格式</h3><table><thead><tr><th style="text-align:center">deviceId</th><th style="text-align:center">alarmDate</th><th style="text-align:center">alarmValueAvgDaily</th></tr></thead><tbody><tr><td style="text-align:center">设备ID</td><td style="text-align:center">告警日期</td><td style="text-align:center">当日平均监测值</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022-1-8</td><td style="text-align:center">27</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022-4-5</td><td style="text-align:center">12</td></tr><tr><td style="text-align:center">u03</td><td style="text-align:center">2022-3-2</td><td style="text-align:center">45</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022-2-10</td><td style="text-align:center">66</td></tr><tr><td style="text-align:center">u02</td><td style="text-align:center">2022-1-18</td><td style="text-align:center">98</td></tr><tr><td style="text-align:center">u01</td><td style="text-align:center">2022-1-28</td><td style="text-align:center">53</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td><td style="text-align:center"></td></tr></tbody></table><h3 id="%E7%BB%9F%E8%AE%A1%E4%B9%8B%E5%90%8E%E6%A0%BC%E5%BC%8F-4" tabindex="-1">统计之后格式</h3><table><thead><tr><th style="text-align:center">类型</th><th style="text-align:center">总数</th><th style="text-align:center">总均值</th></tr></thead><tbody><tr><td style="text-align:center">所有告警设备</td><td style="text-align:center">18398</td><td style="text-align:center">34</td></tr><tr><td style="text-align:center">活跃告警设备</td><td style="text-align:center">3213</td><td style="text-align:center">87</td></tr></tbody></table><h3 id="%E5%B7%A5%E4%BD%9C%E6%80%9D%E8%B7%AF-4" tabindex="-1">工作思路</h3><ol><li>首先使用<code>group by</code>去除重复日期的重复数据，用<code>max</code>函数取最大值</li><li>然后使用<code>group by</code>去除重复设备数，分别查询设备总数和总平均值，再用左连接将查询结果拼接，保存结果查询</li><li>接着处理统计活跃告警设备，先用<code>row_number</code>函数查询分组编号，再使用<code>date_sub</code>函数用告警日期减去分组编号，得出一组临时告警日期用于判定是否是活跃告警设备</li><li>如果有连续相同日期说明是活跃告警设备，所以接着使用<code>count</code>函数和<code>having</code>条件统计过滤有大于等于三天的连续相同日期的设备与告警日期，注意同时要计算均值</li><li>左后统计活跃告警设备总数和平均值，并和第二步中的结果<code>union all</code>即可</li></ol><h3 id="%E5%B7%A5%E4%BD%9C%E8%AF%AD%E5%8F%A5-4" tabindex="-1">工作语句</h3><pre><code class="language-sql">WITH -- 首先去除重复日期的重复数据，这里取最大值t1 AS(SELECT deviceId,alarmDate,MAX(alarmValueAvgDaily) AS alarmValueAvgDailyFROM test_03GROUP BY deviceId, alarmDate),-- 去除重复设备数t2 AS(SELECT *FROM t1GROUP BY deviceId),-- 查询设备总数t3 AS(SELECT &#39;告警设备总数与均值&#39; AS type,COUNT(deviceId) AS allDeviceCountFROM t2),-- 查询总均值t4 AS(SELECT ROUND(AVG(alarmValueAvgDaily)) AS alarmValueAvgAllFROM t1),-- 查询分组后的排序编号t5 AS(SELECT *,ROW_NUMBER() OVER(PARTITION BY deviceId ORDER BY alarmDate) AS alarmDateRankFROM t1),-- 查询告警日期减去分组后排序编号之后的日期，如果有连续相同的说明是连续的天数t6 AS(SELECT *,DATE_SUB(alarmDate, INTERVAL alarmDateRank DAY) AS alarmDateSubFROM t5),-- 查询连续天数大于3天的设备，以及这些活跃设备的平均值t7 AS(SELECT deviceId,ROUND(AVG(alarmValueAvgDaily))  AS alarmValueAvgActive,alarmDateSub,COUNT(*) AS  alarmDateSubCountFROM t6GROUP BY deviceId, alarmDateSubHAVING alarmDateSubCount&gt;=3  ),t8 AS(SELECT &#39;活跃告警设备总数与均值&#39; AS type,COUNT(deviceId) AS allDeviceCount,ROUND(AVG(alarmValueAvgActive)) AS alarmValueAvgActiveAllFROM t7)-- 统计完成所有告警设备以及平均监测值SELECT * FROM t3 LEFT JOIN t4 ON t4.alarmValueAvgAll IS NOT NULLUNION ALL-- 统计完成活跃告警设备以及平均监测值SELECT * FROM t8;</code></pre><h4 id="%E5%90%88%E5%B9%B6%E6%93%8D%E4%BD%9C%E7%AC%A6union%E5%92%8Cunion-all%E4%B9%8B%E9%97%B4%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">合并操作符<code>union</code>和<code>union all</code>之间的区别</h4><ol><li>相同之处<ul><li>都是用于合并两个或多个<code>select</code>语句的结果组合成单个结果集</li><li>操作符内部的每个<code>select</code>语句必须拥有相同数量的，列也必须拥有相似的数据类型，同时每个<code>select</code>语句中的列的顺序必须相同</li></ul></li><li>不同之处<ul><li>对重复结果的处理：<code>union</code>在进行表连接后会筛选掉重复的记录，<code>union all</code>不会去除重复记录</li><li>对排序的处理：<code>union</code>将会按照字段的顺序进行排序，<code>union all</code>只是简单的将两个结果合并后就返回</li><li>从效率上说，<code>union all</code>要比 <code>union</code>快很多，所以，如果可以确认合并的两个结果集中不包含重复数据且不需要排序时的话，那么就使用<code>union all</code></li></ul></li></ol><h4 id="hive%E5%92%8Cmysql%E4%B8%AD%E7%9A%84%E6%97%A5%E6%9C%9F%E5%87%BD%E6%95%B0" tabindex="-1">Hive和MySQL中的日期函数</h4><ol><li><a href="https://www.runoob.com/sql/sql-dates.html" target="_blank">MySQL Date 函数</a>、<a href="https://www.runoob.com/mysql/mysql-functions.html" target="_blank">MySQL 日期函数</a></li><li><a href="https://blog.csdn.net/u013421629/article/details/80450047" target="_blank">【hive 日期函数】Hive常用日期函数整理</a></li><li><em>后期切记整理链接资料，若忘记请读者提醒！！！感谢！！！</em></li></ol><h2 id="%E4%B8%83%E3%80%81%E7%BB%9F%E8%AE%A12022%E5%B9%B41%E6%9C%888%E6%97%A5%E4%B8%8B%E5%8D%8816%E7%82%B9-17%E7%82%B9%EF%BC%8C%E6%AF%8F%E4%B8%AA%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E9%87%8Ftop10%E7%9A%84ip%E5%9C%B0%E5%9D%80" tabindex="-1">七、统计2022年1月8日下午16点-17点，每个接口调用量top10的ip地址</h2><h3 id="%E5%8E%9F%E5%A7%8B%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F-5" tabindex="-1">原始数据格式</h3><table><thead><tr><th style="text-align:center">time</th><th style="text-align:center">interface</th><th style="text-align:center">ip</th></tr></thead><tbody><tr><td style="text-align:center">时间</td><td style="text-align:center">接口</td><td style="text-align:center">访问IP</td></tr><tr><td style="text-align:center">2021/1/8 15:01:28</td><td style="text-align:center">/api/user/login</td><td style="text-align:center">110.25.3.56</td></tr><tr><td style="text-align:center">2021/1/8 15:21:12</td><td style="text-align:center">/api/device/alarm</td><td style="text-align:center">23.21.33.87</td></tr><tr><td style="text-align:center">2021/1/8 15:51:34</td><td style="text-align:center">/api/device/record</td><td style="text-align:center">45.76.21.543</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td><td style="text-align:center"></td></tr></tbody></table><h3 id="%E7%BB%9F%E8%AE%A1%E4%B9%8B%E5%90%8E%E6%A0%BC%E5%BC%8F-5" tabindex="-1">统计之后格式</h3><table><thead><tr><th style="text-align:center">接口</th><th style="text-align:center">访问IP</th><th style="text-align:center">访问次数</th><th style="text-align:center">排名</th></tr></thead><tbody><tr><td style="text-align:center">/api/user/login</td><td style="text-align:center">110.25.3.56</td><td style="text-align:center">89</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">/api/device/alarm</td><td style="text-align:center">23.21.33.87</td><td style="text-align:center">123</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">/api/device/record</td><td style="text-align:center">45.76.21.543</td><td style="text-align:center">23</td><td style="text-align:center">1</td></tr><tr><td style="text-align:center">…</td><td style="text-align:center">…</td><td style="text-align:center">…</td><td style="text-align:center">…</td></tr></tbody></table><h4 id="%E6%AD%A4%E9%A2%98%E4%BD%9C%E4%B8%BA%E5%BC%80%E6%94%BE%E9%A2%98%E4%BE%9B%E5%A4%A7%E5%AE%B6%E6%9F%A5%E9%98%85%EF%BC%8C%E5%90%8E%E9%9D%A2%E6%9C%89%E7%A9%BA%E5%86%8D%E7%BB%A7%E7%BB%AD%E5%86%99" tabindex="-1">此题作为开放题供大家查阅，后面有空再继续写</h4><h2 id="%E9%99%84%E5%BD%95%E8%B5%84%E6%96%99" tabindex="-1">附录资料</h2><h3 id="hive%E5%92%8Cmysql%E4%B8%AD%E9%83%A8%E5%88%86%E5%87%BD%E6%95%B0%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">Hive和MySQL中部分函数的区别</h3><ol><li>date_format()<ul><li>Hive <code>date_format(date date / timestamp time / string 'xxxx-xx-xx', format 'yyyy-MM-dd')</code>，只能识别用<code>-</code>连接的日期字符串</li><li>MySQL <code>date_format(date, format)</code>，具体的format规则请查询<a href="http://c.biancheng.net/mysql/date_format.html" target="_blank">参考资料</a></li></ul></li><li>date_sub()<ul><li>Hive <code>date_sub(date date / timestamp time, int days)</code></li><li>MySQL <code>date_sub(date, interval 时间间隔 type)</code>，具体的type规则请查询<a href="https://www.runoob.com/sql/func-date-sub.html" target="_blank">参考资料</a></li></ul></li></ol><h3 id="hive%E5%92%8Cmysql%E5%B8%B8%E7%94%A8%E6%97%A5%E6%9C%9F%E5%87%BD%E6%95%B0" tabindex="-1">Hive和MySQL常用日期函数</h3><ol><li><code>date_add()</code> 向日期添加指定的时间间隔</li><li><code>date_sub()</code> 从日期减去指定的时间间隔</li><li><code>datediff()</code> 返回两个日期之间的天数</li></ol><h3 id="hive%E4%B8%ADorder-by%2Fdistribute-by%2Fsort-by%2Fgroup-by%2Fpartition-by%E4%B9%8B%E9%97%B4%E7%9A%84%E5%8C%BA%E5%88%AB%E8%AF%B4%E6%98%8E" tabindex="-1">Hive中<code>order by</code>/<code>distribute by</code>/<code>sort by</code>/<code>group by</code>/<code>partition by</code>之间的区别说明</h3><ol><li>order by<ul><li><code>order by</code>会对数据进行全局排序，和oracle、mysql等数据库中的<code>order by</code>效果一样</li><li>需要注意的是，hive执行过程中它只在一个<code>reduce</code>中进行，所以数据量特别大的时候效率非常低</li><li><code>group by</code>分组之后是会组内聚合的，而<code>distribute by</code>和<code>partition by</code>仅仅是分组了，并未有聚合操作</li></ul></li><li>distribute by<ul><li><code>distribute by</code>是控制在<code>map</code>端如何拆分数据给<code>reduce</code>端的</li><li>hive会根据<code>distribute by</code>后面列，对应<code>reduce</code>的个数进行分发，默认是采用<code>hash算法</code></li></ul></li><li>sort by<ul><li><code>sort by</code>为每个<code>reduce</code>产生一个排序文件</li><li>在有些情况下，你需要控制某个特定行应该到哪个<code>reducer</code>，这通常是为了进行后续的聚集操作<code>distribute by</code>刚好可以做这件事</li><li>因此，<code>distribute by</code>经常和<code>sort by</code>配合使用</li></ul></li><li>group by<ul><li>和<code>distribute by</code>类似 都是按<code>key值</code>划分数据 都使用<code>reduce</code>操作</li><li>唯一不同的是，<code>distribute by</code>只是单纯的分散数据，<code>distribute by col</code> 是按照<code>col列</code>把数据分散到不同的<code>reduce</code></li><li>而<code>group by</code>把相同<code>key值</code>的数据聚集到一起，后续必须是聚合操作</li></ul></li><li>cluster by<ul><li>按列分桶建表使用</li><li><code>distribute by</code> 和 <code>sort by</code> 合用就相当于<code>cluster by</code>，但是<code>cluster by</code>不能指定排序为<code>asc(升序)</code>或<code>desc(倒序)</code>的规则，只能是升序排列</li></ul></li><li>partition by<ul><li>按所分区名分区建表使用</li><li>通常查询时会对整个数据库查询，而这带来了大量的开销，因此引入了<code>partition</code>的概念</li><li>在建表的时候通过设置<code>partition</code>的字段，会根据该字段对数据分区存放，更具体的说是存放在不同的文件夹</li><li>这样通过指定设置<code>partition</code>的字段条件查询时可以减少大量的开销</li><li>区内排序用<code>order by</code></li></ul></li></ol><h3 id="mysql%E5%A4%9A%E8%A1%A8%E6%9F%A5%E8%AF%A2%E6%97%B6%E5%A6%82%E4%BD%95%E5%B0%86null%E7%BD%AE%E4%B8%BA0" tabindex="-1">MySQL多表查询时如何将<code>NULL</code>置为<code>0</code></h3><p>使用IFNULL<code>(&quot;字段&quot;, 0)</code>函数即可</p><h3 id="hive%E4%B8%AD%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86null%E5%80%BC%E5%92%8C%E7%A9%BA%E5%AD%97%E7%AC%A6%E4%B8%B2" tabindex="-1">Hive中如何处理NULL值和空字符串</h3><ol><li>Hive表中默认将<code>NULL</code>存为<code>\N</code>，可查看表的源文件（<code>hadoop fs -cat</code>或者<code>hadoop fs -text</code>），文件中存储大量<code>\N</code>，这样造成浪费大量空间</li><li>但Hive的<code>NULL</code>有时候是必须的<ul><li>Hive中<code>insert</code>语句必须列数匹配，不支持不写入，没有值的列必须使用<code>NULL</code>占位</li><li>Hive表的数据文件中按分隔符区分各个列，空列会保存<code>NULL（\n）</code>来保留列位置，<br />但外部表加载某些数据时如果列不够，如表13列，文件数据只有2列，则在表查询时表中的末尾剩余列无数据对应，自动显示为<code>NULL</code></li></ul></li><li>所以，NULL转化为空字符串，可以节省磁盘空间<ul><li>建表时直接指定</li></ul><pre><code class="language-"># 第一种方式ROW FORMAT SERDE &#39;org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe&#39;  WITH SERDEPROPERTIES (&#39;serialization.null.format&#39; = &#39;&#39;)  # 第二种方式 ROW FORMAT DELIMITED NULL DEFINED AS &#39;&#39;  </code></pre><ul><li>修改已存在的表</li></ul><pre><code class="language-">ALTER TABLE hive_tb SET SERDEPROPERTIES(&#39;serialization.null.format&#39; = &#39;&#39;);</code></pre></li><li>使用函数处理NULL值<ul><li><code>NVL(expr1,expr2)</code> 如果第一个参数为<code>NULL</code>那么显示第二个参数的值，如果第一个参数的值不为<code>NULL</code>，则显示第一个参数本来的值</li><li><code>Coalesce(expr1, expr2, expr3….. exprn)</code> 返回表达式中第一个非空表达式，如果所有自变量均为<code>NULL</code>，则 COALESCE 返回<code>NULL</code></li></ul><pre><code class="language-">SELECT COALESCE(NULL,null,3,4,5); 　　-- 结果为：3SELECT COALESCE(NULL,null,&#39;&#39;,3,4,5);   -- 结果为：&#39;&#39;SELECT COALESCE(NULL,null,null,NULL);  -- 结果为：null</code></pre></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llBigData/blob/main/note/hive/hive-sql-inwork.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[详解在Linux中同时安装配置并使用 MySQL5.7 和 MySQL8.0  ]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/linux-install-mysql-with2version" />
                <id>tag:https://fx67ll.xyz,2022-04-30:linux-install-mysql-with2version</id>
                <published>2022-04-30T04:02:34+08:00</published>
                <updated>2025-07-03T10:39:44+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>最近需要使用mysql8.0版本，但是原本的mysql5.7版本已经被多个服务依赖，于是想想能不能同一台服务器装多个版本的mysql，一查确实可行，这里做一个记录方便自己后期回忆</p><h4 id="%E9%98%85%E8%AF%BB%E6%9C%AC%E6%96%87%E5%89%8D%E8%AF%B7%E6%B3%A8%E6%84%8F%EF%BC%81%EF%BC%81%EF%BC%81" tabindex="-1">阅读本文前请注意！！！</h4><ol><li>本文是帮助您建立在<code>mysql5.7</code>版本已经安装完成并在运行中，另外安装配置<code>mysql8.0</code>版本</li><li>如果需要同时安装两个版本，可以先查阅我之前所写关于<code>mysql5.7</code>版本的安装，完成后再按本文继续操作即可</li></ol><h3 id="%E5%AE%89%E8%A3%85%E7%8E%AF%E5%A2%83" tabindex="-1">安装环境</h3><p>CentOS7 + MySQL8.0</p><h3 id="%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E5%8C%85" tabindex="-1">下载安装包</h3><ol><li>点击进入<a href="https://dev.mysql.com/downloads/mysql/" target="_blank">MySQL Community Server 8.0.29 官网下载地址</a></li><li>点击下拉选择 <code>Linux - Generic</code>，找到名为 <code>mysql-8.0.29-linux-glibc2.12-x86_64.tar.xz</code> 点击下载即可</li></ol><h2 id="%E7%AE%80%E5%8C%96%E7%89%88%E5%91%BD%E4%BB%A4%E6%AD%A5%E9%AA%A4" tabindex="-1">简化版命令步骤</h2><p><em>给我自己的提醒：原来的<code>mysql5.7</code>配置基本没有动，服务也没有关，下面的命令都是关于<code>mysql8.0</code>的，原<code>mysql5.7</code>的配置文件在<code>/etc/my.cnf</code></em></p><ol><li>解压xz文件为tar文件，注意命令无过程显示需要等待窗口跳至下一行<ul><li><code>xz -d /usr/soft/sort/mysql-8.0.29-linux-glibc2.12-x86_64.tar.xz -C /usr/soft/sort/</code></li><li><code>tar -xvf /usr/soft/sort/mysql-8.0.29-linux-glibc2.12-x86_64.tar -C /usr/soft/install/</code></li></ul></li><li>赋予权限 <code>chown -R mysql.mysql /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64</code></li><li>修改配置文件 <code>vi /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/my.cnf</code><pre><code class="language-">[client]port=3307# mysql57 不要这个mysqlx_port=33070socket=/tmp/mysql80.sock# mysql57 不要这个mysqlx_socket=/tmp/mysqlx80.sock[mysqld]# skip-grant-tables# mysql安装目录basedir=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64# mysql数据库目录datadir=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/dataport=3307innodb_file_per_table=1character-set-server=utf8# mysql57 不要这个mysqlx_port=33070socket = /tmp/mysql80.sock# mysql57 不要这个mysqlx_socket=/tmp/mysqlx80.sock[mysqld_safe]# 错误日志log-error=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/data/error.log# pid文件pid-file=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/data/mysqld.pidtmpdir=/tmp/mysql80</code></pre></li><li>赋予配置文件权限 <code>chmod 755 /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/my.cnf</code></li><li>修改连接服务文件 <code>vi /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/support-files/mysql.server</code><pre><code class="language-"># 这两项在开头比较好找basedir=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64datadir=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/data# 这项默认的不用找lockdir=&#39;/var/lock/subsys&#39;# 这项默认有但是需要在最后改个80lock_file_path=&quot;$lockdir/mysql80&quot;# 下面两个藏在下面细心的找一下mysqld_pid_file_path=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/data/mysqld.pidconf=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/my.cnf</code></pre></li><li>复制注册连接服务文件  <code>cp -i /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/support-files/mysql.server /etc/init.d/mysql80</code></li><li>初始化mysql80 <code>/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/bin/mysqld --defaults-file=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/my.cnf --user=mysql  --initialize</code><ul><li>root@localhost: 初始密码</li></ul></li><li>启动mysql80服务 <code>service mysql80 start</code>，然后要记得开放防火墙的3307端口才能使用外部连接</li><li>登录 <code>/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/bin/mysql --socket=/tmp/mysql80.sock -u root -p'初始密码'</code></li><li>重置密码，登录后依次执行命令<ul><li><code>flush privileges;</code></li><li><em>注意：新版本mysql8后，不再支持 <code>password()</code>方法，只能通过 <code>alter</code>语句进行修改</em></li><li><code>ALTER USER 'root'@'localhost' IDENTIFIED BY '你的密码';</code></li><li>如果不行可以试试 <code>ALTER USER 'root'@'%' IDENTIFIED BY '你的密码';</code></li><li><code>use mysql;</code></li><li><code>update user set host='%' where user='root' and host='localhost';</code></li><li><code>flush privileges;</code></li></ul></li><li>查看占用端口<code>show global variables like 'port';</code>，如果发现为0，需要执行以下步骤修改<ul><li>停止服务：<code>service mysql80 stop</code></li><li>修改配置文件：<code>vi /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/my.cnf</code>，将 [mysqld] 下的 skip-grant-tables 注释</li><li>重新启动服务：<code>service mysql80 start</code></li></ul></li></ol><h4 id="%E5%BC%80%E5%90%AF3307%E7%AB%AF%E5%8F%A3%E6%B5%81%E7%A8%8B" tabindex="-1">开启3307端口流程</h4><ol><li><code>cd /etc/sysconfig</code>进入该目录，检查是否存储了<code>iptables</code>文件</li><li><code>vim iptables</code>使用<code>vim编辑器</code>修改<code>iptables</code>文件，按下<code>i</code>进入编辑模式</li><li>在初始端口那行下面添加<code>-A INPUT -p tcp -m state --state NEW -m tcp --dport 3307 -j ACCEPT</code>，开放3307端口</li><li><code>service iptables restart</code>重启防火墙即可</li></ol><h4 id="%E5%A6%82%E4%BD%95mysql80%E6%9C%8D%E5%8A%A1%E5%90%AF%E5%8A%A8%E5%A4%B1%E8%B4%A5" tabindex="-1">如何mysql80服务启动失败</h4><p>可以参考一下步骤重新初始化数据库，亲测有效</p><ol><li>删除data文件夹 <code>rm -rf /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/data</code></li><li>新建data目录 <code>mkdir /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/data</code></li><li>赋予权限 <code>chown -R mysql.mysql /usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64</code></li><li>重新初始化 <code>/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/bin/mysqld --defaults-file=/usr/soft/install/mysql-8.0.29-linux-glibc2.12-x86_64/my.cnf --user=mysql  --initialize</code></li><li>测试启动服务 <code>service mysql80 start</code>，成功即可</li></ol><h4 id="%E5%8F%82%E8%80%83%E6%96%87%E6%A1%A3" tabindex="-1">参考文档</h4><ol><li><a href="https://blog.csdn.net/qq_43800252/article/details/113817371" target="_blank">linux 下同时安装 mysql5.7 和 mysql8.0</a></li><li><a href="https://blog.csdn.net/qq_40800602/article/details/106499000" target="_blank">linux安装两个mysql(8.0和5.7)，并同时使用</a></li><li><a href="https://blog.csdn.net/duyudong0425/article/details/113928414" target="_blank">tar.xz文件如何解压</a></li><li><a href="https://blog.csdn.net/u011908518/article/details/119632672" target="_blank">linux mysql8 初始密码修改</a></li><li><a href="https://www.csdn.net/tags/MtzaAgysMTE5MjAtYmxvZwO0O0OO0O0O.html" target="_blank">linux下查看mysql端口号和修改端口号方法</a></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/blob/master/serve-blog/2022/2022-04/linux-install-mysql-with2version.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[pm2 常用命令]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/pm2-shell" />
                <id>tag:https://fx67ll.xyz,2022-04-28:pm2-shell</id>
                <published>2022-04-28T11:15:54+08:00</published>
                <updated>2025-07-03T10:39:49+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h4 id="先说一些废话">先说一些废话</h4><p>之前学习<code>Nodejs</code>项目的时候使用了<code>pm2</code>作为生产环境的进程管理工具，最近服务器崩了需要重启一些服务，发现有些命令记得不是特别清楚，所以这里写一篇文章帮助自己记忆整理一下<code>pm2</code>的常用命令，后续有需要查阅一下即可~</p><h2 id="常用命令">常用命令</h2><ol><li>进入bin目录启动：<code>pm2 start www</code> / <code>pm2 start app.js</code></li><li><code>pm2 start app.js --name=&quot;fx67ll&quot;</code> 启动并命名为fx67ll，没有命名的话后续可以用id替代name</li><li><code>pm2 start app.js --watch</code> 当文件变化时自动重启应用</li><li><code>pm2 start script.sh</code> 启动bash脚本</li><li><code>pm2 list</code> 查看所有启动的应用列表</li><li><code>pm2 monit</code> 显示每个应用程序的CPU和内存占用情况</li><li><code>pm2 show [app-id/app-name]</code> 显示指定应用程序的所有信息</li><li><code>pm2 log</code> 显示应用程序的日志信息</li><li><code>pm2 log [app-id/app-name]</code> 显示指定应用程序的日志信息</li><li><code>pm2 flush</code> 清空所有日志文件</li><li><code>pm2 stop all</code> 停止所有应用程序</li><li><code>pm2 stop [app-id/app-name]</code> 停止指定应用程序</li><li><code>pm2 restart all</code> 重启所有应用程序</li><li><code>pm2 restart [app-id/app-name]</code> 重启指定应用程序</li><li><code>pm2 delete all</code>  关闭并删除所有应用程序</li><li><code>pm2 delete [app-id/app-name]</code> 删除指定的应用程序</li><li><code>pm2 reset [app-id/app-name]</code> 重置重启数量</li><li><code>pm2 startup</code> 创建开机自启动命令</li><li><code>pm2 save</code> 保存当前应用列表</li><li><code>pm2 resurrect</code> 重新加载保存的应用列表</li><li><code>pm2 update</code> 保存进程，杀死并重启进程，一般用于更新pm2版本</li><li><code>pm2 ecosystem</code> 生成一个示例json配置文件</li><li><em>更多命令</em>可以参考<a href="https://pm2.keymetrics.io/docs/usage/quick-start/">pm2官方文档</a></li></ol><h3 id="使用均衡负载模式cluster-mode的相关命令">使用均衡负载模式(cluster mode)的相关命令</h3><ol><li><code>pm2 start app.js -i n</code> 均衡负载模式(cluster mode)启动n个app.js应用实例</li><li><code>pm2 reload all</code> 重启均衡负载模式(cluster mode)下的所有应用</li><li><code>pm2 gracefulReload all</code> Graceful reload all apps in cluster mode</li><li><code>pm2 scale [app-id/app-name] 10</code> 将指定的应用程序拓展到10个实例</li></ol><h3 id="0秒停机重新加载集群模式下可以达到重启时不停止服务">0秒停机重新加载(集群模式下，可以达到重启时不停止服务)</h3><ol><li><code>pm2 reload app.js</code>  重新启动所有进程，始终保持至少一个进程在运行</li><li><code>pm2 gracefulReload all</code>  优雅地以集群模式重新加载所有应用程序</li></ol><h2 id="pm2配置文件">pm2配置文件</h2><h3 id="生成示例配置文件">生成示例配置文件</h3><pre><code>// 生成一个示例json配置文件pm2 ecosystem// pm2初始化pm2 init</code></pre><h3 id="配置项">配置项</h3><ol><li>基础类<ul><li><code>name</code>：进程名</li><li><code>script</code>：node启动文件的路径</li><li><code>cwd</code> ：项目所在的目录</li><li><code>args</code> ：通过命令行传递给node启动文件的参数</li><li><code>interpreter</code> ：编译器的绝对路径（默认node）</li><li><code>interpreter_args</code> ：传给编译器的参数</li><li><code>node_args</code>：传给node的参数</li></ul></li><li>进阶类<ul><li><code>instances</code> ：进程数</li><li><code>exec_mode</code> ：进程的模式（cluster或fork）</li><li><em>PS:</em> cluster模式利用node的child_process模块孵化多个子进程，主进程监听端口，子进程只和主进程通信，从而达到单个端口多个进程；通过轮转方式实现负载均衡</li><li><code>watch</code> ：布尔值或文件数组，允许开启监听文件改动重启</li><li><code>ignore_watch</code> ：不监听的文件</li><li><code>max_memory_restart</code> ：超过该内存就自动重启</li><li><code>env</code> ：应用中的默认环境变量</li><li><code>env_</code> ：命令行中可传入的环境变量，覆盖默认环境变量</li><li><code>source_map_support</code> ：默认true，支持sourcemap文件</li></ul></li><li>日志类<ul><li><code>log_date_format</code> ：日志时间格式</li><li><code>error_file</code> ：错误日志存放路径</li><li><code>out_file</code> ：全部日志存放路径</li><li><code>combine_logs</code>：是否将不同id的进程日志合并</li><li><code>merge_logs</code>：同上</li></ul></li><li>控制流<ul><li><code>min_uptime</code> ：pm2认为进程在线的最小时长</li><li><code>listen_timeout</code> ：如果app没有发送ready信号，间隔多长时间reload</li><li><code>kill_timeout</code> ：从告诉进程要关闭到强制关闭进程的间隔时间</li><li><code>wait_ready</code>：是否等待进程发送ready信号</li><li><code>max_restarts</code> ：最大不稳定重启次数（不稳定指的是小于1s或者小于的<code>min_uptime</code>重启）</li><li><code>restart_delay</code>：进程掉线后，等待多长时间重启</li><li><code>autorestart</code>： 是否开启自动重启</li></ul></li></ol><h4 id="配置项实践中需要注意的内容">配置项实践中需要注意的内容</h4><ol><li><code>script</code>：若使用cluster模式，必须是启动文件入口，不可通过npm启动</li><li><code>max_restarts</code>：指不稳定重启，即小于1s或<code>min_uptime</code>的重启，要结合<code>min_uptime</code>配置才起效</li><li><code>listen_timeout</code>：当cluster模式时，这个值要大于一个进程启动所需时间，否则reload时会造成短暂的服务不可用</li></ol><h3 id="配置文件示例">配置文件示例</h3><pre><code>module.exports = {    apps : [{        name      : 'API',      //应用名        script    : 'app.js',   //应用文件位置        env: {            PM2_SERVE_PATH: &quot;.&quot;,    //静态服务路径            PM2_SERVE_PORT: 8080,   //静态服务器访问端口            NODE_ENV: 'development' //启动默认模式        },        env_production : {            NODE_ENV: 'production'  //使用production模式 pm2 start ecosystem.config.js --env production        },        instances:&quot;max&quot;,          //将应用程序分布在所有CPU核心上,可以是整数或负数        watch:true,               //监听模式        output: './out.log',      //指定日志标准输出文件及位置        error: './error.log',     //错误输出日志文件及位置，pm2 install pm2-logrotate进行日志文件拆分        merge_logs: true,         //集群情况下，可以合并日志        log_type:&quot;json&quot;,          //日志类型        log_date_format: &quot;DD-MM-YYYY&quot;,  //日志日期记录格式    }],    deploy : {        production : {            user : 'node',                      //ssh 用户            host : '212.83.163.1',              //ssh 地址            ref  : 'origin/master',             //GIT远程/分支            repo : 'git@github.com:repo.git',   //git地址            path : '/var/www/production',       //服务器文件路径            post-deploy : 'npm install &amp;&amp; pm2 reload ecosystem.config.js --env production'  //部署后的动作        }    }};</code></pre><h3 id="配置启动命令packagejson">配置启动命令（package.json）</h3><pre><code># pm2-server工程的环境变量，目的是区分各个环境的应用启动路径# cross-env NODE_ENV=development# pm2的启动命令# pm2 start pm2-conf/ecosystem.config.js# 传递给pm2的参数，-- only  &lt;name&gt;，--env &lt;env name&gt;# --only  detective  --env testcross-env NODE_ENV=development   pm2 start pm2-conf/ecosystem.config.js   --only  detective  --env test</code></pre><h2 id="关于pm2">关于pm2</h2><h3 id="pm2是什么">pm2是什么</h3><p>pm2（Process Manager 2）是具有内置负载均衡器的Node.js应用程序的生产运行时和进程管理器。<br />它允许您永久保持应用程序活跃，无需停机即可重新加载它们，并促进常见的Devops任务。</p><h3 id="pm2特性">pm2特性</h3><ol><li>后台运行：普通启动方式：<code>node index.js</code>关闭终端就结束进程，<code>pm2</code>可以后台运行，终端关闭不影响</li><li>日志管理：应用程序日志保存在服务器的硬盘中<code>~/.pm2/logs/</code></li><li>负载均衡：<code>pm2</code>可以通过创建共享同一服务器端口的多个子进程来扩展您的应用程序，这样做还允许您以零秒停机时间重新启动应用程序</li><li>终端监控：提供实时的接口，可以在终端中监控您的应用程序并检查应用程序运行状况（CPU使用率，使用的内存，请求/分钟等）</li><li>SSH部署：自动部署，避免逐个在所有服务器中进行<code>ssh</code></li><li>静态服务：支持静态服务器功能</li><li>多平台支持：适用于<code>Linux</code>（稳定）和<code>macOS</code>（稳定）和<code>Windows</code>（稳定）</li><li>集成管理：对于多个进程，不同环境，可以统一配置，方便管理</li></ol><h3 id="pm2安装">pm2安装</h3><p>使用npm命令<code>npm install pm2</code>即可，配置项参考npm的方式</p><h2 id="附录">附录</h2><h3 id="参考资料">参考资料</h3><ol><li><a href="https://pm2.keymetrics.io/docs/usage/quick-start/">参考教程 ———— pm2 官方教程</a></li><li><a href="https://www.jianshu.com/p/9cab8f7020c1">参考文档 ———— PM2 常用命令</a></li><li><a href="https://blog.csdn.net/cs380637384/article/details/82682799">参考文档 ———— pm2介绍及使用手册</a></li><li><a href="https://www.jianshu.com/p/1778deeb428e">参考文档 ———— pm2入坑详解</a></li></ol><p>我是 <a href="https://fx67ll.com">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llNode/blob/main/node-note/pm2.md">本文github仓库地址</a>，为我点一颗Star，Thanks~ :)<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[工作中常用Less知识点实践总结]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/less-in-work" />
                <id>tag:https://fx67ll.xyz,2022-04-26:less-in-work</id>
                <published>2022-04-26T23:55:25+08:00</published>
                <updated>2025-07-02T11:26:27+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h2 id="我所理解的less的一些好处">我所理解的Less的一些好处</h2><ol><li>函数式编程css</li><li>自定义变量用于整体主题调整</li><li>嵌套语法简化开发复杂度</li></ol><h2 id="mixin的写法">mixin的写法</h2><pre><code class="language-less">.defaultBorder(@width: 10px, @style: solid, @color: red){  border: @width @style @color;}</code></pre><h2 id="when条件判断函数">when条件判断函数</h2><pre><code class="language-less">.area(@width) when(@width &lt;= 200px){  width: @width;  background-color: yellow;}@media screen and (max-width: 200px){  .when{.area(100px);  }}@media screen and (min-width: 201px){  .when{.area(210px);  }}</code></pre><h2 id="loop循环函数">loop循环函数</h2><pre><code class="language-less">.widthFun(100);.widthFun(@n, @i:10) when (@i &lt;= @n){  width-@{i}{  width: (@i * 100% / @n);  }  .widthFun(@n,(@i+10))}// 上面这段loop编程成css:width-10 {  ·width: 10%;}width-20 {  ·width: 20%;}width-30 {  ·width: 30%;}width-40 {  ·width: 40%;}width-50 {  ·width: 50%;}width-60 {  ·width: 60%;}width-70 {  ·width: 70%;}width-80 {  ·width: 80%;}width-90 {  ·width: 90%;}width-100 {  ·width: 100%;}</code></pre><h2 id="常用的数值计算函数">常用的数值计算函数</h2><pre><code class="language-less">// unit()对数值连接单位/去除单位width: unit(@num, px);// round()对数值四舍五入取整数width: round(@num);// ceil()对数值向下取整数width: ceil(@num);// floor()对数值向上取整数width: floor(@num);// percentage()把小数转化为百分比width: percentage(@num);// abs()对数值取绝对值width: unit(abs(@num));// lighten()颜色提亮color: lighten(@color, 10%);// darken()颜色变暗background-color: darken(@bgColor, 20%);</code></pre><h2 id="less的匹配模式类似于函数重载">Less的匹配模式（类似于函数重载）</h2><pre><code class="language-less">// 示例：方向不同的样式三角形.sanjiao(down ,@w: 100px, @c:#ff6600){  border-width: @w;  border-color: @c transparent transparent transparent;  border-style: solid;}.sanjiao(top ,@w: 100px, @c:#ff6600){  border-width: @w;  border-color: transparent transparent @c transparent;  border-style: solid;}.sanjiao(left ,@w: 100px, @c:#ff6600){  border-width: @w;  border-color: transparent  @c  transparent transparent;  border-style: solid;}.sanjiao(right ,@w: 100px, @c:#ff6600){  border-width: @w;  border-color: transparent transparent transparent @c ;  border-style: solid;}#tranigle{  width: 0;  height: 0;  overflow: hidden;  .sanjiao(right)}</code></pre><h2 id="less中的论据">Less中的论据</h2><p>直接使用函数默认数值</p><pre><code class="language-less">.border(@s: solid, @c :#ff6600, @h:10px){  border: @arguments;}</code></pre><h2 id="less中免编译">Less中免编译</h2><p>具体的使用场景就是calc在less中的使用，calc在scss中可以按照css的样式直接去写</p><pre><code class="language-less">.width{  // css3中的计算属性 calc  width: calc(300px * 0.3);       // ~'XXX'指示less不要编译XXX而是直接把XXX写入css文件中  width: ~'calc(300px * 0.3)';  }</code></pre><h2 id="less中的important">Less中的<code>!important</code></h2><pre><code class="language-less">.sss{  width: 100px;  height: 100px;  border: 10px;}.box{  // 编译成css后，每个.sss中的属性后面都加上了 !important  .sss !important; }</code></pre><h2 id="less中的其他函数">Less中的其他函数</h2><p><strong>可以参考<a href="https://less.bootcss.com/functions/">官网</a>的文档详细学习</strong></p><h2 id="less在实际应用中的注意事项">Less在实际应用中的注意事项</h2><h4 id="less中运算符号都会被计算使用属性一些特殊值的时候会出现一些问题">Less中运算符号都会被计算，使用属性一些特殊值的时候会出现一些问题</h4><p>这个示例是在做egg形状的时候需要用到的属性，如果直接使用<code>/</code>会导致<code>50% / 60%</code>被计算成为0.8333333%显示在样式中，这样就会出现问题，<br />所以需要使用转义符来保证这个属性的值原样输出，一开始做的时候没有注意到这点，搜索<code>less转义符</code>，<br />看到<a href="https://www.cnblogs.com/96net/p/13879749.html">这个介绍</a>才反应过来</p><pre><code>// border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;border-radius: ~&quot;50% 50% 50% 50% / 60% 60% 40% 40%&quot;;</code></pre><h4 id="less在vue中使用的时候需要在mainjs中注册sass不用">Less在Vue中使用的时候需要在<code>main.js</code>中注册，Sass不用</h4><p>我是 <a href="https://fx67ll.com">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llHCLS/blob/master/Less/less.md">本文github仓库地址</a>，为我点一颗Star，Thanks~ :)<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Azkaban快速入门]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/azkaban-quickstart" />
                <id>tag:https://fx67ll.xyz,2022-04-26:azkaban-quickstart</id>
                <published>2022-04-26T16:52:04+08:00</published>
                <updated>2025-07-03T10:42:15+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h4 id="%E5%85%88%E8%AF%B4%E4%B8%80%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说一些废话</h4><p>因为之前自己工作中有用过Azkaban作为自动化任务调度工具，所以想参考自己之前的使用经验，总结一下关于Azkaban的使用，方便大家使用Azkaban快速实现企业级自动化任务</p><h2 id="%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E5%B8%82%E9%9D%A2%E4%B8%8A%E7%9A%84%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%B7%A5%E5%85%B7" tabindex="-1">如何选择市面上的任务调度工具</h2><ol><li>简单的任务<br />Linux crontab是用来定期执行程序的命令</li><li>复杂的任务<br /><strong>Oozie/Azkaban/Airflow/DolphinScheduler</strong><br />Azkaban 是一个分布式工作流管理程序，解决Hadoop工作依赖性问题<br />Oozie 相比 Azkaban 是一个重量级的任务调度系统，功能全面，但配置使用也更复杂<br />Airflow 使用python脚本<br />DolphinScheduler 使用可视化的方式操作，国产，所以现在比较火</li></ol><h3 id="azkaban%E5%92%8Coozie%E4%B9%8B%E9%97%B4%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">Azkaban和Oozie之间的区别</h3><p>总体来说，ooize相比azkaban是一个重量级的任务调度系统，功能全面，但配置使用也更复杂，<br />如果可以不在意某些功能的缺失，轻量级调度器azkaban是很不错的候选对象</p><ol><li>功能<ul><li>两者均可以调度linux、mapreduce、pig、spark、java等脚本工作流任务</li><li>两者均可以定时执行工作流任务</li></ul></li><li>工作流定义<ul><li>Azkaban使用Properties文件定义工作流</li><li>Oozie使用XML文件定义工作流</li></ul></li><li>工作流传参<ul><li>Azkaban支持直接传参，例如<code>${input}</code></li><li>Oozie支持参数和EL表达式，例如<code>${fs:dirSize(myInputDir)}</code></li></ul></li><li>定时执行<ul><li>Azkaban的定时执行任务是基于时间的</li><li>Oozie的定时执行任务基于时间和输入数据，功能强大，但是带来配置的复杂度比较高</li></ul></li><li>资源管理<ul><li>Azkaban有较严格的权限控制，如用户对工作流进行读/写/执行等操作</li><li>Oozie暂无严格的权限控制，多人协作就比较麻烦了</li></ul></li><li>工作流执行<ul><li>Azkaban有三种运行模式：solo server mode、 two server mode、multiple executor mode</li><li>Oozie作为工作流服务器运行，支持多用户和多工作流</li></ul></li><li>工作流管理<ul><li>Azkaban支持浏览器以及ajax方式操作工作流</li><li>Oozie支持命令行、HTTP REST、Java API、浏览器操作工作流</li></ul></li><li>依赖<ul><li>Azkaban依赖于MySql</li><li>Oozie依赖于hadoop</li></ul></li></ol><h2 id="%E4%BB%80%E4%B9%88%E6%98%AFazkaban" tabindex="-1">什么是Azkaban</h2><p>Azkaban是由Linkedin公司推出的一个批量工作流任务调度器，主要用于在一个工作流内以一个特定的顺序运行一组工作和流程，<br />它的配置是通过简单的key:value对的方式，通过配置中的dependencies 来设置依赖关系。<br />Azkaban使用job配置文件建立任务之间的依赖关系，并提供一个易于使用的web用户界面维护和跟踪你的工作流。</p><h2 id="azkaban%E7%9A%84%E7%89%B9%E7%82%B9" tabindex="-1">Azkaban的特点</h2><ol><li>兼容任何版本的hadoop</li><li>易于使用的Web用户界面</li><li>简单的工作流的上传</li><li>方便设置任务之间的关系</li><li>调度工作流</li><li>模块化和可插拔的插件机制</li><li>认证/授权(权限的工作)</li><li>能够杀死并重新启动工作流</li><li>有关失败和成功的电子邮件提醒</li></ol><h2 id="%E6%9E%B6%E6%9E%84" tabindex="-1">架构</h2><h4 id="azkabanwebserver" tabindex="-1">AzkabanWebServer</h4><p><code>AzkabanWebServer</code>是整个Azkaban工作流系统的主要管理者，它用户登录认证、负责project管理、定时执行工作流、跟踪工作流执行进度等一系列任务</p><h4 id="azkabanexecutorserver" tabindex="-1">AzkabanExecutorServer</h4><p>负责具体的工作流的提交、执行，它们通过mysql数据库来协调任务的执行</p><h4 id="%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%88mysql%EF%BC%89" tabindex="-1">关系型数据库（MySQL）</h4><p>存储大部分执行流状态，<code>AzkabanWebServer</code>和<code>AzkabanExecutorServer</code>都需要访问数据库</p><h2 id="%E9%83%A8%E7%BD%B2%E6%A8%A1%E5%BC%8F" tabindex="-1">部署模式</h2><ol><li>Solo<ul><li>WebServer和ExecServer都启动在一个JVM中，就一个进程</li><li>使用内置的H2数据库来存储元数据</li></ul></li><li>Two Server<ul><li>一台机器模式：只有一台ExecutorServer</li><li>WebServer和ExecutorServer是不同的独立的进程</li><li>使用MySQL存储元数据</li></ul></li><li>Multiple Executor<ul><li>从3.+ 开始，支持多个Executor</li><li>多台机器模式：多个ExecutorServer</li><li>WebServer和Executor可以不在一台机器上</li><li>使用MySQL存储元数据</li></ul></li></ol><h4 id="%E4%BD%BF%E7%94%A8%E5%A4%9Aexecutor%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1">使用多Executor模式的注意事项</h4><p>为确保所选的 Executor 能够准确的执行任务，我们须在以下两种方案任选其一，推荐使用方案二</p><ol><li>方案一：指定特定的 Executor（hadoop101）去执行任务<ul><li>在<code>MySQL</code>中<code>Azkaban</code>数据库<code>executors</code>表中，查询<code>hadoop101上的Executor的id</code></li><li>在执行工作流程时加入<code>useExecutor</code>属性</li></ul></li><li>方案二：在<code>Executor</code>所在所有节点部署任务所需脚本和应用</li></ol><h2 id="%E5%AE%89%E8%A3%85%E4%BE%9D%E8%B5%96%E5%8C%85%E5%8F%8A%E5%85%B6%E4%BD%9C%E7%94%A8" tabindex="-1">安装依赖包及其作用</h2><pre><code class="language-"># 该包中就是所有的建表语句，主要是配置 MySQLazkaban-db-3.84.4.tar.gz  # 执行服务器（Executor）配置azkaban-exec-server-3.84.4.tar.gz  # 服务器（Web）配置azkaban-web-server-3.84.4.tar.gz  </code></pre><h2 id="%E8%AE%BF%E9%97%AE%E7%AB%AF%E5%8F%A3%E5%8F%B7" tabindex="-1">访问端口号</h2><p>默认是<em>8443</em>，可以通过修改配置文件<code>azkaban.properties</code>的方式更改端口号<br />端口号使用规则：<code>jetty.ssl.port &gt; jetty.port</code><br />但是使用<code>jetty.ssl.port</code>的前提是<code>jetty.use.ssl = true</code>，这个配置表示开启ssl安全套接层，否则使用jetty.port端口</p><pre><code class="language-"># 示例配置文件jetty.use.ssl=falsejetty.maxThreads=25jetty.ssl.port=8443jetty.port=8081jetty.keystore=keystorejetty.password=passwordjetty.keypassword=passwordjetty.truststore=keystorejetty.trustpassword=password</code></pre><h2 id="%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8" tabindex="-1">基本使用</h2><h3 id="%E4%B8%BB%E8%A6%81%E5%8A%9F%E8%83%BD" tabindex="-1">主要功能</h3><ol><li>Projects：最重要的部分，创建一个工程，所有<code>flows</code>将在工程中运行</li><li>Scheduling: 显示定时任务</li><li>Executing: 显示当前运行的任务</li><li>History: 显示历史运行任务</li></ol><h3 id="%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B%E6%AD%A5%E9%AA%A4" tabindex="-1">基本流程步骤</h3><ol><li>首先需要说明的是因为利用界面化操作，所以相关的文件直接在本地windows系统里去编辑，创建，打包zip即可</li><li>创建<code>xxx.project</code>工程文件<pre><code class="language-"># 作用：  # 表示采用新的 Flow-API 方式解析 flow 文件# 内容：  # 表示当前解析 flow 文件的 azkaban 版本为 2.0azkaban-flow-version: 2.0</code></pre></li><li>创建<code>xxx.flow</code>流程任务文件<pre><code class="language-"># 作用：  # 表示作业调度过程# 内容：  # yaml 语法的编写  # name 表示 job 的名称  # type 表示 job 的类型  # command 表示你要执行作业的方式为命令，这里意思输入Hello Worldnodes:  - name: jobAtype: commandconfig:  command: echo &quot;Hello World&quot;</code></pre></li><li>将上述两个文件压缩成一个<code>.zip</code>文件，并上传 <em>需要注意的是：压缩包的文件名称必须是英文</em></li><li>上传后，如果想看<code>Job</code>的内容是什么，可以在<code>Job Command</code>中可以查看解析出任务内容</li><li>点击<code>Flows</code>中<code>Command</code>任务，可以进入到任务的具体界面，<code>Execute</code>可以执行任务，<code>Schedul</code>可以进行定时调度</li><li>执行后的任务中，点击<code>Job List</code>可以查看执行日志，<code>Flow Log</code>可以查看流程日志，绿色表示成功，蓝色表示正在执行，红色表示执行失败</li><li>任务执行后，可以在<code>History</code>中查看任务历史记录</li></ol><h3 id="%E5%B8%B8%E8%A7%81%E4%BB%BB%E5%8A%A1%E7%B1%BB%E5%9E%8B" tabindex="-1">常见任务类型</h3><h4 id="%E6%89%A7%E8%A1%8C-shell-%E5%91%BD%E4%BB%A4" tabindex="-1">执行 shell 命令</h4><pre><code class="language-">type=commandcommand=echo &#39;hello-world&#39;</code></pre><h4 id="%E6%89%A7%E8%A1%8C-shell-%E8%84%9A%E6%9C%AC" tabindex="-1">执行 shell 脚本</h4><pre><code class="language-">type=commandcommand=sh hello-world.sh</code></pre><h4 id="%E6%89%A7%E8%A1%8C-spark-%E7%A8%8B%E5%BA%8F" tabindex="-1">执行 Spark 程序</h4><pre><code class="language-">type=commandcommand=/usr/install/spark/bin/spark-submit --class com.test.AzkabanTest test-0.1.0.jar</code></pre><h4 id="%E6%89%A7%E8%A1%8C-hive-%E5%91%BD%E4%BB%A4%E3%80%81%E8%84%9A%E6%9C%AC" tabindex="-1">执行 hive 命令、脚本</h4><pre><code class="language-">type=commandcommand=beeline -u jdbc:hive://localhost:7777 -n hive -p hive -f &#39;test.sql&#39;</code></pre><h4 id="%E6%89%A7%E8%A1%8C-mapreduce-%E7%A8%8B%E5%BA%8F" tabindex="-1">执行 MapReduce 程序</h4><pre><code class="language-">type=commandcommand=${HADOOP_HOME}bin/hadoop jar hadoop-mapreduce-examples--0.1.0.jarmapreduce-test ${input} ${output}</code></pre><h3 id="%E5%A4%9A%E4%BB%BB%E5%8A%A1%E4%BE%9D%E8%B5%96%E6%A1%88%E4%BE%8B" tabindex="-1">多任务依赖案例</h3><p>使用<code>dependsOn</code>属性来表示依赖，他的值是一个数组</p><pre><code class="language-"># 示例 basic.flow# JobA 和 JobB 执行完了，才能执行 JobCnodes:  - name: jobA  type: command  config:    command: echo &quot;I’m JobA&quot;  - name: jobB  type: command  config:    command: echo &quot;I’m JobB&quot;  - name: jobC    type: command    # jobC 依赖 JobA 和 JobB    dependsOn:      - jobA      - jobB    config:      command: echo &quot;I’m JobC&quot;</code></pre><h3 id="%E5%A4%B1%E8%B4%A5%E9%87%8D%E8%AF%95" tabindex="-1">失败重试</h3><h4 id="%E8%87%AA%E5%8A%A8%E5%A4%B1%E8%B4%A5%E9%87%8D%E8%AF%95" tabindex="-1">自动失败重试</h4><p>使用<code>retries</code>和<code> retry.backoff</code>来配置重试次数，重试的时间间隔</p><pre><code class="language-"># 示例 basic.flow，在任务中配置nodes:  - name: JobA    type: command    config:      # 执行脚本      command: sh xxx.sh  # 重试次数 3次      retries: 3  # 重试间隔时间 10000ms 也就是10s      retry.backoff: 10000  # 示例 basic.flow，在flow的全局配置config:  retries: 3  retry.backoff: 10000nodes:  - name: jobA    type: command    config:      command: sh xxxq.sh  - name: jobB  type: command  config:    command: sh xxxb.sh  - name: jobC    type: command    dependsOn:      - jobA      - jobB    config:      command: sh xxxc.sh</code></pre><h4 id="%E6%89%8B%E5%8A%A8%E5%A4%B1%E8%B4%A5%E9%87%8D%E8%AF%95" tabindex="-1">手动失败重试</h4><p>在<code>Flow View</code>界面右键点击需要重拾的<code>Job</code>，选择需要的重新执行的某个过程，<code>Enable</code>和<code>Disable</code>下面都分别有如下参数：</p><ol><li>Parents：该作业的上一个任务</li><li>Ancestors：该作业前的所有任务</li><li>Children：该作业后的一个任务</li><li>Descendents：该作业后的所有任务</li><li>Enable All：所有的任务</li></ol><h3 id="%E8%BF%90%E8%A1%8Cjava%E4%B8%BB%E7%B1%BB%E6%96%B9%E6%B3%95" tabindex="-1">运行Java主类方法</h3><p>JavaProcess 类型可以运行一个自定义主类方法，type 类型为 javaprocess，可用的配置为：</p><ul><li>Xms：最小堆</li><li>Xmx：最大堆</li><li>classpath：类路径</li><li>java.class：要运行的<code>Java对象</code>，其中必须包含<code>Main</code>方法</li><li>main.args：<code>Main</code>方法的参数</li></ul><h4 id="%E8%BF%90%E8%A1%8Cjava%E4%B8%BB%E7%B1%BB%E6%96%B9%E6%B3%95%E6%A1%88%E4%BE%8B" tabindex="-1">运行Java主类方法案例</h4><pre><code class="language-"># 示例 basic.flownodes:  - name: test_java    type: javaprocess    config:      Xms: 96M      Xmx: 200M      java.class: com.fx67ll.springboot.AzkabanTest</code></pre><h3 id="%E4%BD%BF%E7%94%A8%E6%9D%A1%E4%BB%B6%E5%B7%A5%E4%BD%9C%E6%B5%81" tabindex="-1">使用条件工作流</h3><h4 id="%E4%BD%BF%E7%94%A8%E8%BF%90%E8%A1%8C%E6%97%B6%E5%8F%82%E6%95%B0%E6%9D%A5%E4%BD%BF%E7%94%A8%E6%9D%A1%E4%BB%B6" tabindex="-1">使用运行时参数来使用条件</h4><ol><li>基本原理<ul><li>父Job将参数写入 <code>JOB_OUTPUT_PROP_FILE</code> 所指向的环境变量文件</li><li>子Job使用EL表达式 <code>${jobName:param}</code> 来获取父Job输出的参数并定义执行条件</li></ul></li><li>支持的条件参数<pre><code class="language-">== 等于!= 不等于&gt; 大于&gt;= 大于等于&lt; 小于&lt;= 小于等于&amp;&amp; 与|| 或! 非</code></pre></li><li>案例：JobB依赖JobA，但是JobB不需要每天都执行，只需要每个周一执行<pre><code class="language-"># 示例 JobA.shecho &quot;do JobA&quot;# 获取当前是周几wk=&#96;date + %w&#96;echo &quot;{\&quot;wk\&quot;:$wk}&quot; &gt;$JOB_OUTPUT_PROP_FILE# 示例 JobB.shecho &quot;do JobB&quot;# 示例 basic.flownodes:  - name: jobAtype: commandconfig:  command: sh JobA.sh  - name: jobBtype: commanddependsOn:  - jobAconfig:  command: sh JobB.sh  condition: ${JobA:wk} == 1</code></pre></li></ol><h4 id="%E4%BD%BF%E7%94%A8%E9%A2%84%E5%AE%9A%E4%B9%89%E5%AE%8F" tabindex="-1">使用预定义宏</h4><p>Azkaban 中预置了几个特殊的判断条件，称为预定义宏，预定义宏会根据所有父 Job 的完成情况进行判断，再决定是否执行，可用的预定义宏如下：<br />```<br /># all_success: 表示父 Job 全部成功才执行(默认)<br /># all_done：表示父 Job 全部完成才执行<br /># all_failed：表示父 Job 全部失败才执行<br /># one_success：表示父 Job 至少一个成功才执行<br /># one_failed：表示父 Job 至少一个失败才执行</p><pre><code># 示例 JobA.shecho &quot;do JobA&quot;# 示例 JobB.shecho &quot;do JobB&quot;# 示例 JobC.shecho &quot;do JobC&quot;# 示例 basic.flownodes:  - name: jobAtype: commandconfig:  command: sh JobA.sh  - name: jobBtype: commandconfig:  command: sh JobA.shdependsOn:  - jobA  - jobBconfig:  command: sh JobC.sh  condition: one_success  # 提交的时候故意不提交 JobB.sh，以测试预定义宏是否生效  ```</code></pre><h3 id="%E5%AE%9A%E6%97%B6%E6%89%A7%E8%A1%8C" tabindex="-1">定时执行</h3><p>在执行工作流时候，选择左下角<code>Schedule</code>按钮，在<code>Schedule Flow Options</code>配置即可</p><h3 id="%E5%91%8A%E8%AD%A6" tabindex="-1">告警</h3><h4 id="%E9%82%AE%E4%BB%B6%E5%91%8A%E8%AD%A6" tabindex="-1">邮件告警</h4><p><strong>可以参考视频教程————<a href="https://www.bilibili.com/video/BV1KS4y1M7yQ?p=17" target="_blank">大数据Azkaban教程</a>详细学习，这里后期会补上说明</strong></p><h4 id="%E7%94%B5%E8%AF%9D%E5%91%8A%E8%AD%A6" tabindex="-1">电话告警</h4><p><strong>可以参考视频教程————<a href="https://www.bilibili.com/video/BV1KS4y1M7yQ?p=18" target="_blank">大数据Azkaban教程</a>详细学习，这里后期会补上说明</strong></p><h2 id="yaml" tabindex="-1">YAML</h2><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFyaml" tabindex="-1">什么是YAML</h3><p>YAML（YAML 不是标记语言）是一种非常灵活的格式，几乎是 JSON 的超集，已经被用在一些著名的项目中，如 Travis CI、Circle CI 和 AWS CloudFormation。<br />YAML 的库几乎和 JSON 一样无处不在。除了支持注释、换行符分隔、多行字符串、裸字符串和更灵活的类型系统之外，YAML 也支持引用文件，以避免重复代码。</p><h3 id="yaml%E7%AE%80%E4%BB%8B" tabindex="-1">YAML简介</h3><ol><li>YAML语言的设计参考了JSON，XML和SDL等语言，YAML 强调以数据为中心，简洁易读，编写简单</li><li>YAML 语言（发音 /ˈjæməl/ ）的设计目标，就是方便人类读写，它实质上是一种通用的数据串行化格式</li><li>YAML 有一个小的怪癖，所有的 YAML 文件开始行都应该是 <code>---</code>，这是 YAML 格式的一部分， 表明一个文件的开始<br /><strong>有意思的命名</strong><br />YAML 全称是 “YAML Ain’t a Markup Language”（YAML不是一种置标语言）的递归缩写。<br />在开发的这种语言时，YAML 的意思其实是：“Yet Another Markup Language”（仍是一种置标语言）</li></ol><h3 id="%E8%AF%AD%E6%B3%95%E7%89%B9%E7%82%B9" tabindex="-1">语法特点</h3><pre><code class="language-">大小写敏感通过缩进表示层级关系禁止使用 tab 缩进，只能使用空格键缩进的空格数目不重要，只要相同层级左对齐即可使用 # 表示注释</code></pre><h3 id="%E6%94%AF%E6%8C%81%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84" tabindex="-1">支持的数据结构</h3><ol><li>对象：键值对的集合，又称为映射（mapping）/ 哈希（hashes） / 字典（dictionary）</li><li>数组：一组按次序排列的值，又称为序列（sequence） / 列表（list）</li><li>纯量（scalars）：单个的、不可再分的值</li></ol><h3 id="%E8%AF%AD%E6%B3%95%E8%AF%B4%E6%98%8E" tabindex="-1">语法说明</h3><ol><li>引号<pre><code class="language-">a. 双引号&quot;&quot;：不会转义字符串里面的特殊字符，特殊字符作为本身想表示的意思。name: &quot;123\n123&quot; 输出： 123 换行 123b. 单引号&#39;&#39;：会将字符串里面的特殊字符转义为字符串处理name: &quot;123\n123&quot;输出： 123\n123c. 如果不加引号将会转义特殊字符，当成字符串处理</code></pre></li><li>文本块<pre><code class="language-">a. |：使用|标注的文本内容缩进表示的块，可以保留块中已有的回车换行value: |   hello   world!输出：hello 换行 world！b. +表示保留文字块末尾的换行，-表示删除字符串末尾的换行  value: |hellovalue: |-hellovalue: |+hello输出：hello\n hello hello\n\n(有多少个回车就有多少个\n)注意 &quot;|&quot; 与 文本之间须另起一行c. &gt;：使用 &gt; 标注的文本内容缩进表示的块，将块中回车替换为空格，最终连接成一行value: &gt; helloworld!输出：hello 空格 world！注意 &quot;&gt;&quot; 与 文本之间的空格，使用定界符&quot;&quot;（双引号）、&#39;&#39;（单引号）或回车表示的块，最终表示成一行  </code></pre></li><li>锚点与引用<pre><code class="language-">使用 &amp; 定义数据锚点（即要复制的数据），使用 * 引用锚点数据（即数据的复制目的地）  name: &amp;a yamlbook: *abooks:    - java   - *a   - python输出book： yaml输出books：[java,yaml,python]注意 * 引用部分不能追加内容</code></pre></li><li>纯量与数据类型约定<pre><code class="language-">a. 纯量是最基本的、不可再分的值  b. 字符串使用&#39;&#39;或&quot;&quot;或不使用引号value0: &#39;hello World!&#39;value1: &quot;hello World!&quot;value2: hello World!c. 布尔值true或false表示  d. 数字12 # 整数 014 # 八进制整数 0xC ＃ 十六进制整数 13.4 ＃ 浮点数 1.2e+34 ＃ 指数 .inf空值 ＃ 无穷大c. 空值null或~表示d. 日期使用 iso-8601 标准表示日期date: 2018-01-01t16:59:43.10-05:00在springboot中yaml文件的时间格式 date: yyyy/MM/dd HH:mm:sse. 强制类型转换YAML 允许使用个感叹号!，强制转换数据类型，单叹号通常是自定义类型，双叹号是内置类型  money: !!str123date: !Booleantruef. 内置类型：!!int # 整数类型 !!float # 浮点类型 !!bool # 布尔类型 !!str # 字符串类型 !!binary # 也是字符串类型 !!timestamp # 日期时间类型 !!null # 空值 !!set # 集合 !!omap,!!pairs # 键值列表或对象列表!!seq # 序列，也是列表 !!map # 键值表</code></pre></li><li>对象<pre><code class="language-">Map（属性和值）（键值对）的形式：key: 空格 v ：表示一堆键值对，空格不可省略  car:    color: red    brand: BMW一行写法car:{   color: red，brand: BMW}相当于json：{&quot;color&quot;:&quot;red&quot;,&quot;brand&quot;:&quot;BMW&quot;}例如表示url属性值  url: https://www.liuluanyi.cn 转为 JavaScript 如下:{ url: &#39;https://www.liuluanyi.cn&#39;}YAML 也允许另一种写法，将所有键值对写成一个行内对象  host: { ip: 10.1.1.1, port: 2222 } 转为 JavaScript 如下:{ host: { ip: &#39;10.1.1.1&#39;, port: 2222 } }</code></pre></li><li>数组<pre><code class="language-">a. 一组连词线开头的行，构成一个数组  brand:   - audi   - bmw   - ferrari一行写法brand: [audi,bmw,ferrari]相当于json[&quot;auri&quot;,&quot;bmw&quot;,&quot;ferrari&quot;]b. 数组对象：列表中的所有成员都开始于相同的缩进级别，并且使用一个 --- 作为开头  ---ipaddr:- 120.168.117.21- 120.168.117.22- 120.168.117.23转为 JavaScript 如下:ipaddr: [ &#39;120.168.117.21&#39;, &#39;120.168.117.22&#39;, &#39;120.168.117.23&#39; ]c. 数据结构的子成员是一个数组，则可以在该项下面缩进一个空格。- - source - destination - services转为 JavaScript 如下:[ [ &#39;source&#39;, &#39;destination&#39;, &#39;services&#39; ] ]d. 数组也可以采用行内(或者流式)表示法。services: [FTP, SSH]companies: [{id: 1,name: company1,price: 200W},{id: 2,name: company2,price: 500W}]转为 JavaScript 如下:{ services: [ &#39;FTP&#39;, &#39;SSH&#39; ] }{ companies:    [ { id: 1, name: &#39;company1&#39;, price: &#39;200W&#39; },     { id: 2, name: &#39;company2&#39;, price: &#39;500W&#39; } ] }f. 对象和数组复合使用languages: - Ruby - Perl - Python websites: YAML: yaml.org  Ruby: ruby-lang.org  Python: python.org 转为 JavaScript 如下:{ languages: [ &#39;Ruby&#39;, &#39;Perl&#39;, &#39;Python&#39; ],  websites: { YAML: &#39;yaml.org&#39;, Ruby: &#39;ruby-lang.org&#39;, Python: &#39;python.org&#39; } }</code></pre></li><li>常量<pre><code class="language-">布尔值 boolean:     - TRUE  #true,True都可以    - FALSE  #false，False都可以浮点数 float:    - 3.14    - 6.8523015e+5  #可以使用科学计数法整数 int:    - 123    - 0b1010_0111_0100_1010_1110    #二进制表示Null null:    nodeName: &#39;node&#39;    parent: ~  #使用~表示null字符串 string:    - 哈哈    - &#39;Hello world&#39;  #可以使用双引号或者单引号包裹特殊字符    - newline      newline2    #字符串可以拆成多行，每一行会被转化成一个空格时间 date:    - 2018-02-17    #日期必须使用ISO 8601格式，即yyyy-MM-dd日期 datetime:     -  2018-02-17T15:02:31+08:00    #时间使用ISO 8601格式，时间和日期之间使用T连接，最后使用+代表时区转为 JavaScript 如下:{ boolean: [ true, false ],  float: [ 3.14, 685230.15 ],  int: [ 123, 685230 ],  null: { nodeName: &#39;node&#39;, parent: null },  string: [ &#39;哈哈&#39;, &#39;Hello world&#39;, &#39;newline newline2&#39; ],  date: [ Sat Feb 17 2018 08:00:00 GMT+0800 (中国标准时间) ],  datetime: [ Sat Feb 17 2018 15:02:31 GMT+0800 (中国标准时间) ] }</code></pre></li><li>特殊符号总结<pre><code class="language-">a. YAML 允许使用两个感叹号，强制转换数据类型  test1: !!str 123test2: !!str true转为 JavaScript 如下:{ test1: &#39;123&#39;, test2: &#39;true&#39; }b. … 和---配合使用，在一个配置文件中代表一个文件的结束：---time: 20:03:20player: Sammy Sosaaction: strike (miss)...---time: 20:03:47player: Sammy Sosaaction: grand slam...c. &gt;在字符串中折叠换行，| 保留换行符，这两个符号是YAML中字符串经常使用的符号this: |  Foo  Barthat: &gt;  Foo  Bar转为 JavaScript 如下:{ this: &#39;Foo\nBar\n&#39;, that: &#39;Foo Bar\n&#39; }d. 引用，重复的内容在YAML中可以使用&amp;来完成锚点定义，使用 * 来完成锚点引用defaults: &amp;defaults  adapter:  postgres  host:     localhostdevelopment:  database: myapp_development  &lt;&lt;: *defaultstest:  database: myapp_test  &lt;&lt;: *defaults转为 JavaScript 如下:{ defaults: { adapter: &#39;postgres&#39;, host: &#39;localhost&#39; },  development:    { database: &#39;myapp_development&#39;,     adapter: &#39;postgres&#39;,     host: &#39;localhost&#39; },  test:    { database: &#39;myapp_test&#39;,     adapter: &#39;postgres&#39;,     host: &#39;localhost&#39; } }注意，不能独立的定义锚点，比如不能直接这样写： &amp;SS Sammy Sosa另外，锚点能够定义更复杂的内容，比如：default: &amp;default    - Mark McGwire    - Sammy Sosahr: *default那么hr相当于引用了default的数组，注意，hr: *default 要写在同一行  </code></pre></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llBigData/blob/main/note/azkaban/azkaban-quickstart.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[SpringBoot快速入门]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/springboot-quickstart" />
                <id>tag:https://fx67ll.xyz,2022-04-25:springboot-quickstart</id>
                <published>2022-04-25T03:25:15+08:00</published>
                <updated>2025-07-02T11:27:50+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h4 id="%E5%85%88%E8%AF%B4%E4%B8%80%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说一些废话</h4><p>虽然我的工作中更多的是与数据库打交道，但是作为一个<code>Coder</code>，我觉得掌握前后端的<code>Web</code>技术来说是非常有必要的。</p><p>不仅可以帮助我们在工作中更好的理解其他岗位与你对接的人他的工作痛点，也能在公司需要人手的时候成为一个有力的应急帮手，比如之前公司的数据中台我就参与架构和部分开发任务，更重要的是我私下里可以运用一些快速框架来搭建一些有意思的网站，比如我的<a href="https://fx67ll.com" target="_blank">个人主页</a>和<a href="https://fx67ll.xzy" target="_blank">个人博客</a>都是我自学<code>java</code>和<code>js</code>所做出来的作品。</p><p>所以今天我希望整合一些我以往的经验和看过的教程文档，来写一篇文章，帮助你在一天之内通过这篇文章快速学习<code>SpringBoot</code>框架以及各种开发必备的工具与插件！！！</p><h2 id="mvc" tabindex="-1">MVC</h2><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFmvc" tabindex="-1">什么是MVC</h3><ol><li>MVC三层架构是指：视图层 View、服务层 Service，与持久层 Dao，它们分别完成不同的功能<ul><li>View 层：用于接收用户提交请求的代码在这里编写</li><li>Service 层：系统的业务逻辑主要在这里完成</li><li>Dao 层：直接操作数据库的代码在这里编写</li></ul></li><li>为了更好的降低各层间的耦合度，在三层架构程序设计中，采用面向抽象编程，即上层对下层的调用，是通过接口实现的，而下层对上层的真正服务提供者，是下层接口的实现类</li><li>服务标准（接口）是相同的，服务提供者（实现类）可以更换，这就实现了层间解耦合</li></ol><h3 id="mvc-%E6%9E%B6%E6%9E%84%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B" tabindex="-1">MVC 架构程序的工作流程</h3><ol><li>用户通过 View 页面向服务端提出请求，可以是表单请求、超链接请求、AJAX 请求等</li><li>服务端 Controller 控制器接收到请求后对请求进行解析，找到相应的 Model 对用户请求进行处理</li><li>Model 处理后，将处理结果再交给 Controller</li><li>Controller 在接到处理结果后，根据处理结果找到要作为向客户端发回的响应 View 页面，页面经渲染（数据填充）后，再发送给客户端</li></ol><h2 id="%E4%BD%BF%E7%94%A8xml%E8%BF%98%E6%98%AF%E6%B3%A8%E8%A7%A3" tabindex="-1">使用xml还是注解</h2><ol><li>应用的基本配置使用xml，比如数据源和资源文件等</li><li>业务开发使用注解，比如service注入bean</li><li>但是xml越来越多导致越来越臃肿，最终发展到使用完全基于注解开发</li></ol><h2 id="%E6%B3%A8%E8%A7%A3" tabindex="-1">注解</h2><h3 id="%E5%A3%B0%E6%98%8Ebean%E6%B3%A8%E8%A7%A3" tabindex="-1">声明Bean注解</h3><blockquote><p>@Component 组件没有明确规定其角色，作用在类级别上声明当前类为一个业务组件，被<code>Spring IOC 容器</code>维护<br />@Service 在业务逻辑层（Service）类级别进行声明<br />@Registory 在数据访问层（Dao）类级别进行声明<br />@Controller 在展现层（MVC）使用，标注当前类为一个控制器</p></blockquote><h3 id="%E6%B3%A8%E5%85%A5bean%E6%B3%A8%E8%A7%A3" tabindex="-1">注入Bean注解</h3><blockquote><p>@Autowired 它可以对类成员变量、方法及构造函数进行标注，完成自动装配的工作，通过<code>@Autowired</code>的使用来消除set、get方法<br />@Inject 作用同上，是<code>JSR-330 标准</code><br />@Resource 作用同上，是<code>JSR-250 标准</code><br /><em>以上三种注解在Set方法或属性上声明，一般情况下更习惯声明在属性上，代码简洁清晰</em></p></blockquote><h3 id="%E9%85%8D%E7%BD%AE%E4%B8%8E%E8%8E%B7%E5%8F%96bean%E6%B3%A8%E8%A7%A3" tabindex="-1">配置与获取Bean注解</h3><blockquote><p>@Configuration 将当前类声明为一个配置类，相当于一个xml配置文件<br />@ComponentScan 自动扫描包下标注有@Repository @Service @Controller<br />@Component 注解的类并有<code>Spring IOC 容器</code>进行实例化和维护<br />@Bean 作用于方法上，声明当前方法的返回值是一个<code>Bean对象</code>，相当于<code>xml文件</code>中<code>&lt;bean&gt;</code>声明当前方法返回一个<code>bean对象</code><br />@Value 获取<code>properties文件</code>指定的<code>key/value</code></p></blockquote><h4 id="pom.xml" tabindex="-1">pom.xml</h4><p>作用是添加坐标相关配置，主要是各种依赖jar包</p><h3 id="%E7%BB%84%E5%90%88%E6%B3%A8%E8%A7%A3%E5%92%8C%E5%85%83%E6%B3%A8%E8%A7%A3" tabindex="-1">组合注解和元注解</h3><p>所谓元注解其实就是可以注解到别的注解上的注解，被注解的注解称之为组合注解，组合注解具备元注解的功能，主要的作用是消除重复注解</p><h3 id="%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3" tabindex="-1">自定义注解</h3><p>个性化的定义自己所需要的功能并声明一个注解，简化工程，可以参考文章————<a href="https://www.cnblogs.com/mizhiniurou/p/10890951.html" target="_blank">SPRINGBOOT自定义注解</a>学习</p><h3 id="%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3" tabindex="-1">常用注解</h3><p><strong>可以参考文章————<a href="https://blog.csdn.net/qq_53324833/article/details/121079368" target="_blank">SpringBoot常用注解集合</a>详细学习，这里后期会补上说明</strong></p><h4 id="%40restcontroller%E3%80%81%40responsebody%E3%80%81%40requestbody" tabindex="-1">@RestController、@ResponseBody、@RequestBody</h4><ol><li>相当于<code>@Controller + @ResponseBody</code>两个注解的结合，返回<code>JSON</code>数据不需要在方法前面加<code>@ResponseBody</code>注解了，<br />但使用@RestController这个注解，就不能返回jsp、html页面，视图解析器无法解析jsp、html页面v</li><li><code>@ResponseBody</code>表示该方法的返回结果直接写入<code>HTTP response body</code>中，一般在异步获取数据时使用（也就是AJAX），<br />在使用<code>@RequestMapping</code>后，返回值通常解析为跳转路径，但是加上<code>@ResponseBody</code>后返回结果不会被解析为跳转路径，而是直接写入<code>HTTP response body</code>中，<br />比如异步获取<code>JSON</code>数据，加上<code>@ResponseBody</code>后，会直接返回<code>JSON</code>数据</li><li><code>@RequestBody</code>将 HTTP 请求正文插入方法中，使用适合的 HttpMessageConverter 将请求体写入某个对象</li></ol><h4 id="%40mapperscan%E3%80%81%40mapper" tabindex="-1">@MapperScan、@Mapper</h4><ol><li>@Mapper注解：<ul><li>作用：在接口类上添加了@Mapper，在编译之后会生成相应的接口实现类</li><li>添加位置：接口类上面</li><li>如果想要每个接口都要变成实现类，那么需要在每个接口类上加上<code>@Mapper</code>注解，比较麻烦，解决这个问题用<code>@MapperScan</code>注解</li></ul></li><li>@MapperScan注解：<ul><li>作用：指定要变成实现类的接口所在的包，然后包下面的所有接口在编译之后都会生成相应的实现类</li><li>添加位置：是在Springboot启动类上面添加</li><li>添加<code>@MapperScan(&quot;com.winter.da&quot;)</code>注解以后，<code>com.winter.dao</code>包下面的接口类，在编译之后都会生成相应的实现类</li></ul></li></ol><h2 id="%E4%B9%A0%E6%83%AF%E5%A4%A7%E4%BA%8E%E9%85%8D%E7%BD%AE%E7%9B%AE%E6%A0%87" tabindex="-1">习惯大于配置目标</h2><p>Spring Boot 的目标是快速运行，快速创建web应用，并独立机型部署（jar包方式，war包方式），相比于Spring框架是全新重写的框架</p><h2 id="%E6%A0%B8%E5%BF%83%E9%85%8D%E7%BD%AE" tabindex="-1">核心配置</h2><h3 id="%E4%BF%AE%E6%94%B9banner%E5%9B%BE%E6%A0%87" tabindex="-1">修改Banner图标</h3><p>主要是通过修改<code>/src/main/resources</code>目录下的<code>banner.txt</code>文件，如果没有则默认使用SpringBoot初始Banner<br />可以<a href="http://patorjk.com/software/taag/#p=display&amp;f=Graffiti&amp;t=Type%20Something%20" target="_blank">个性化制作Banner的网站</a>制定相应的txt文件</p><h3 id="%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE" tabindex="-1">全局配置</h3><p>默认是<code>application.properties</code>或者<code>application.yml</code><br />坐标依赖都配置在<code>pom.xml</code>中，如果添加了依赖以后标红可以使用<code>Maven -&gt; Reload project</code>即可</p><h4 id="%E5%85%A5%E5%8F%A3%E7%B1%BB%E4%BE%9D%E9%9D%A0%E7%BB%84%E5%90%88%E6%B3%A8%E8%A7%A3%40springbootapplication" tabindex="-1">入口类依靠组合注解<code>@SpringBootApplication</code></h4><blockquote><p>@SpringBootConfiguration 本身是一个配置类，启动类启动的时候会加载<br />@EnableAutoConfiguration 组合了<code>@AutoConfigurationPackage</code>&amp;<code>@Import(AutoConfigurationImportSelector.class)</code><br />@AutoConfigurationPackage 底层是一个@Import(AutoConfigurationPackage.Registrar.class)，其会把启动类的包下组合都扫描到Spring容器中<br />@AutoConfigurationImportSelector 读取大量的自动配置类，完成自动配置，其读取的是classpath下的<code>META-INF/spring.factories</code>下的配置文件</p></blockquote><pre><code class="language-">@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(    excludeFilters = {@Filter(    type = FilterType.CUSTOM,    classes = {TypeExcludeFilter.class}), @Filter(    type = FilterType.CUSTOM,    classes = {AutoConfigurationExcludeFilter.class})})</code></pre><h3 id="profile%E9%85%8D%E7%BD%AE%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E5%8C%BA%E5%88%86%E7%94%9F%E4%BA%A7%E5%92%8C%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83" tabindex="-1">Profile配置————区分生产和开发环境</h3><p>通过在<code>application.yml</code>中设置<code>spring.profiles.active=test/dev/prod</code>来动态切换不同环境，例如：</p><pre><code class="language-"># 开发环境配置文件application-dev.ymlserver:  prot: 8098# 测试环境配置文件application-test.ymlserver:  prot: 8097# 生产环境配置文件application-prod.ymlserver:  prot: 8099# 主配置文件application.ymlspring:  profiles:    active: dev</code></pre><h3 id="%E6%97%A5%E5%BF%97%E9%85%8D%E7%BD%AE" tabindex="-1">日志配置</h3><p>SpringBoot默认使用<code>LogBack</code>日志系统，一般主流的日志都是用<code>log4j</code>日志系统</p><h4 id="%E5%A6%82%E6%9E%9C%E9%87%8D%E5%A4%8D%E5%90%AF%E5%8A%A8spring%E9%A1%B9%E7%9B%AE%EF%BC%8C%E5%8F%AF%E8%83%BD%E4%BC%9A%E6%9C%89%E7%AB%AF%E5%8F%A3%E5%8D%A0%E7%94%A8%E7%9A%84%E6%8A%A5%E9%94%99" tabindex="-1">如果重复启动Spring项目，可能会有端口占用的报错</h4><ol><li>思路是杀死占用端口的进程即可，主要是下面两个命令</li><li>使用<code>netstat -aon|findstr &quot;被占用的端口&quot;</code>或者<code>tasklist |findstr &quot;进程名称&quot;</code>查询到端口的进程号</li><li>使用<code>taskkill /f /t /im &quot;进程名称&quot;</code>或者<code>taskkill /f /t /pid &quot;进程PID&quot;</code>杀死进程即可</li></ol><h2 id="%E4%BA%8B%E5%8A%A1%E6%8E%A7%E5%88%B6" tabindex="-1">事务控制</h2><h3 id="%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1" tabindex="-1">声明式事务</h3><p><strong>可以参考文章————<a href="https://blog.csdn.net/justry_deng/article/details/80828180" target="_blank">SpringBoot声明式事务的简单运用</a>详细学习，这里后期会补上说明</strong><br />主要应用在新增修改删除上，应用注解即可</p><h2 id="%E5%85%A8%E5%B1%80%E5%BC%82%E5%B8%B8" tabindex="-1">全局异常</h2><h3 id="%E4%BD%BF%E7%94%A8%40controlleradvice%E9%85%8D%E5%90%88%40exceptionhandler" tabindex="-1">使用@ControllerAdvice配合@ExceptionHandler</h3><p><strong>可以参考文章————<a href="https://blog.csdn.net/wangxinyao1997/article/details/103710843" target="_blank">Springboot系列-@ControllerAdvice使用</a>详细学习，这里后期会补上说明</strong><br />此注解其实是一个增强的<code>Controller</code>，使用这个<code>Controller</code>，可实现三个方面的功能，因为这是SpringMVC提供的功能，所以可以在springboot中直接使用</p><ol><li>全局异常处理 （@ExceptionHandler）</li><li>全局数据绑定 （@InitBinder）</li><li>全局数据预处理 （@ModelAttribute）</li></ol><pre><code class="language-">package com.fx67ll.springboot.exceptions;import com.fx67ll.springboot.po.vo.ResultInfo;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvicepublic class TestGlobalExceptionHandler {    @ExceptionHandler(value = Exception.class)    @ResponseBody    public ResultInfo exceptionHandler(Exception exception) {        ResultInfo resultInfo = new ResultInfo();        resultInfo.setCode(978);        resultInfo.setMsg(&quot;全局异常拦截，操作失败！&quot;);//        if (exception instanceof ParamsException) {//            ParamsException paramsException = (ParamsException) exception;//            resultInfo.setMsg(paramsException.getMsg());//            resultInfo.setCode(paramsException.getCode());//        }        return resultInfo;    }}</code></pre><h2 id="%E6%95%B0%E6%8D%AE%E6%A0%A1%E9%AA%8C" tabindex="-1">数据校验</h2><h3 id="%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%BF%9B%E8%A1%8C%E5%90%8E%E7%AB%AF%E6%95%B0%E6%8D%AE%E6%A0%A1%E9%AA%8C" tabindex="-1">为什么要进行后端数据校验</h3><p>数据的校验是交互式网站一个不可或缺的功能，前端的<code>js校验</code>可以涵盖大部分的校验职责，如用户名唯一性，生日格式，邮箱格式校验等等常用的校验。<br />但是一般前端传来的数据是不可信的，前端校验过了，后端也应该重新校验，因为不排除用户绕过浏览器直接通过<code>Http工具</code>向后端请求的情况。<br />所以服务端的数据校验也是必要的，可以防止脏数据落到数据库中，如果数据库中出现一个非法的邮箱格式，也会让运维人员头疼不已。</p><h3 id="%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E5%90%8E%E7%AB%AF%E6%95%B0%E6%8D%AE%E6%A0%A1%E9%AA%8C" tabindex="-1">如何进行后端数据校验</h3><ol><li><code>SpringBoot</code>中一般使用<code>Spring Validation</code>来进行后端数据校验，它是对<code>Hibernate Validation</code>进行了二次封装，<br />在<code>SpringMVC</code>模块中添加了自动校验，并将校验信息封装进了特定的类中</li><li>在使用时我们只需要引入<code>spring-boot-starter-web</code>依赖即可，该模块会自动依赖<code>spring-boot-starter-validation</code></li></ol><h3 id="spring-validation-%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3" tabindex="-1">Spring Validation 常用注解</h3><blockquote><p>@Null：被注释的元素必须为<code>null</code><br />@NotNull：被注释的元素不能为<code>null</code>，可以为空字符串<br />@AssertTrue：被注释的元素必须为<code>true</code><br />@AssertFalse：被注释的元素必须为<code>false</code><br />@Min(value)：被注释的元素必须是一个数字，其值必须大于等于指定的最小值<br />@Max(value)：被注释的元素必须是一个数字，其值必须小于等于指定的最大值<br />@DecimalMin(value)：被注释的元素必须是一个数字，其值必须大于等于指定的最小值<br />@DecimalMax(value)：被注释的元素必须是一个数字，其值必须小于等于指定的最大值<br />@Size(max,min)：被注释的元素的大小必须在指定的范围内<br />@Digits(integer,fraction)：被注释的元素必须是一个数字，其值必须在可接受的范围内<br />@Past：被注释的元素必须是一个过去的日期<br />@Future：被注释的元素必须是一个将来的日期<br />@Pattern(value)：被注释的元素必须符合指定的正则表达式<br />@Email：被注释的元素必须是电子邮件地址<br />@Length：被注释的字符串的大小必须在指定的范围内<br />@Range：被注释的元素必须在合适的范围内<br />@URL：被注解的元素必须是一个<code>URL</code><br />@NotEmpty：用在集合类上，不能为<code>null</code>，并且长度必须大于0<br />@NotBlank：只能作用在<code>String</code>上，不能为<code>null</code>，而且调用<code>trim()</code>后，长度必须大于0</p></blockquote><h3 id="%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3-1" tabindex="-1">自定义注解</h3><p><strong>可以参考文章————<a href="https://blog.csdn.net/ileopard/article/details/123485111" target="_blank">Spring自定义注解(validation)</a>详细学习，这里后期会补上说明</strong></p><h3 id="%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81" tabindex="-1">示例代码</h3><ol><li><code>/com/fx67ll/springboot/controller/UserController.java</code>在传参的位置添加<code>@Vaild</code>注解表示这里的参数需要校验，需要注意JSON格式和表单格式传过来的参数异常会有些区别，需要在后面注意<pre><code class="language-">// 添加用户@PutMapping(&quot;/adduser&quot;)public ResultInfo saveUser(@RequestBody @Valid User user) {    ResultInfo resultInfo = new ResultInfo();    userService.saveUser(user);    return resultInfo;}</code></pre></li><li>在<code>Bean</code>文件<code>/com/fx67ll/springboot/dao/User.java</code>中私有字段上使用注解来校验，不贴所有代码了，仅贴部分重点代码<pre><code class="language-">@NotBlank(message = &quot;用户名称不能为空！&quot;)private String userName;@NotBlank(message = &quot;用户密码不能为空！&quot;)@Length(min = 6, max = 20, message = &quot;密码长度最少六位且最多二十位！&quot;)private String userPwd;</code></pre></li><li>在全局自定义异常拦截中<code>/com/fx67ll/springboot/exceptions/TestGlobalExceptionHandler.java</code>向用户返回错误代码和信息<pre><code class="language-">package com.fx67ll.springboot.exceptions;import com.fx67ll.springboot.po.vo.ResultInfo;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvicepublic class TestGlobalExceptionHandler {@ExceptionHandler(value = Exception.class)@ResponseBodypublic ResultInfo exceptionHandler(Exception exception) {ResultInfo resultInfo = new ResultInfo();resultInfo.setCode(978);resultInfo.setMsg(&quot;全局异常拦截，操作失败！&quot;);// 全局数据校验，注意！！！使用 json 请求体调用接口，校验异常抛出 MethodArgumentNotValidExceptionif (exception instanceof MethodArgumentNotValidException) {MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) exception;resultInfo.setCode(1023);resultInfo.setMsg(methodArgumentNotValidException.getBindingResult().getFieldError().getDefaultMessage());}return resultInfo;}}</code></pre></li></ol><h2 id="%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90" tabindex="-1">静态资源</h2><p>默认配置下，我们可以在<code>resources</code>资源目录下存放web应用静态资源文件<br />自定义静态资源路径，可以通过在<code>spring.resources.static-locations</code>后面追加一个配置<code>classpath:/你自定义的配置目录/</code>，例如：</p><pre><code class="language-"># application.ymlspring:  resources:# 多个目录使用逗号隔开    static-loaction: classpath:/public/,classpath:/static/,classpath:/fx67ll/</code></pre><h2 id="%E6%89%93%E5%8C%85%E5%92%8C%E9%83%A8%E7%BD%B2" tabindex="-1">打包和部署</h2><h3 id="jar%E5%8C%85" tabindex="-1">jar包</h3><ol><li>一般用于编写依赖工具包</li><li>打包<ul><li>在IDEA<code>Run/Debug Configurations</code>下<code>Command line</code>配置<code>clean complie package -Dmaven.test.skip=true</code>执行打包命令</li><li><code>target</code>目录得到待部署的项目文件</li></ul></li><li>部署<ul><li>在dos窗口中，执行命令<code>java -jar jar包所在的本地目录</code></li></ul></li></ol><h3 id="war%E5%8C%85" tabindex="-1">war包</h3><ol><li>在生产环境中最为常见的部署方式</li><li>修改<code>pom.xml</code>，设置打包模式为war包<pre><code class="language-">&lt;groupId&gt;com.fx67ll&lt;/groupId&gt;&lt;artifactId&gt;springboot-quickstart&lt;/artifactId&gt;&lt;version&gt;0.1.0&lt;/version&gt;&lt;!--设置为war包模式--&gt;&lt;packaging&gt;war&lt;/packaging&gt;</code></pre></li><li>忽略内嵌Tomcat<pre><code class="language-">&lt;!--设置为外部已提供，表示忽略--&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-tomcat&lt;/artifactId&gt;&lt;scope&gt;provided&lt;/scope&gt;&lt;/dependency&gt;</code></pre></li><li>配置生成的war包名称<pre><code class="language-">&lt;build&gt;&lt;!--设置war包名称--&gt;    &lt;finalName&gt;fx67ll-springboot-quickstart-test&lt;/finalName&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></li><li>修改<code>Starter</code>类，添加容器启动加载文件（类似读取web.xml文件）<ul><li>这里通过继承<code>SpringBootServletInitiallizer</code>类并重写<code>configure</code>方法来实现</li><li>在部署项目的时候指定外部Tomcat读取项目入口方法</li></ul><pre><code class="language-">@SpringBootApplicationpublic class Starter extends SpringBootServletInitializer {public static void main(String[] args) {SpringApplication.run(Starter.class);}@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {return builder.sources(Starter.class);}}</code></pre></li><li>打包<ul><li>在IDEA<code>Run/Debug Configurations</code>下<code>Command line</code>配置<code>clean complie package -Dmaven.test.skip=true</code>执行打包命令</li><li><code>target</code>目录得到待部署的项目文件</li></ul></li><li>部署并访问<ul><li>放置到外部tomcat中，执行bin目录下start脚本即可</li></ul></li></ol><h3 id="%E7%83%AD%E9%83%A8%E7%BD%B2" tabindex="-1">热部署</h3><p>热部署，就是在应用正在运行的时候升级软件，却不需要重新启动应用，主要应用在开发过程中</p><h4 id="%E7%83%AD%E9%83%A8%E7%BD%B2%E5%8E%9F%E7%90%86" tabindex="-1">热部署原理</h4><ol><li><code>spring-boot-devtools</code>是一个为开发者服务的一个模块，其中最重要的功能就是自动应用代码更改到最新的App上面去，<br />原理是在发现代码有更改之后，重新启动应用，但是速度比手动停止后再启动还要更快，更快指的不是节省出来的手工操作的时间</li><li>其深层原理是使用了两个<code>ClassLoader</code>，一个<code>Classloader</code>加载那些不会改变的类（第三方Jar包），另一个<code>ClassLoader</code>加载会更改的类，称为<code>restart ClassLoader</code>，<br />这样在有代码更改的时候，原来的<code>restart ClassLoader</code>被丢弃，重新创建一个<code>restart ClassLoader</code>，由于需要加载的类相比较少，所以实现了较快的重启时间，<em>大概在5秒以内</em></li></ol><h4 id="devtools%E5%8E%9F%E7%90%86" tabindex="-1">devtools原理</h4><ol><li>devtools会监听classpath下的文件变动，并且会立即重启应用（发生在保存时机）<em>注意：因为其采用的虚拟机机制，该项重启是很快的</em></li><li>devtools可以实现页面热部署（即页面修改后会立即生效，这个可以直接在<code>application</code>文件中配置<code>spring.thymeleaf.cache=false</code>来实现 <em>注意：不同的模板配置不一样</em></li></ol><h4 id="%E7%83%AD%E9%83%A8%E7%BD%B2%E4%B8%BB%E8%A6%81%E6%AD%A5%E9%AA%A4" tabindex="-1">热部署主要步骤</h4><ol><li>在<code>pom.xml</code>中添加依赖，同时添加<code>devtools</code>生效标志插件<pre><code class="language-">&lt;!--热部署插件devtools--&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;&lt;!--表示当前这个项目被继承之后，这个不向下传递--&gt;&lt;optional&gt;true&lt;/optional&gt;&lt;/dependency&gt;&lt;plugin&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;&lt;!--在原有的基础上添加--&gt;&lt;configuration&gt;&lt;!--如果没有该配置，热部署插件devtools不生效--&gt;&lt;fork&gt;true&lt;/fork&gt;&lt;/configuration&gt;&lt;/plugin&gt;</code></pre></li><li>修改<code>application.yml</code>全局配置文件，在<code>application.yml</code>中配置<code>spring.devtools.restart.enable=false</code>，此时<code>restart</code>类加载器还会初始化，但不会监视文件更新<pre><code class="language-">spring:  # 热部署配置  devtools:restart:  enabled: true  # 设置重启的目录，添加目录的文件需要restart  additional-paths: src/main/java  # 解决项目启动重新编译后接口报404的问题  poll-interval: 3000  quiet-period: 1000</code></pre></li><li>修改 IDEA 配置<ul><li>修改了java类之后，IDEA 默认是不自动编译的，而<code>spring-boot-devtools</code>又是监测<code>classpath</code>下的文件发生变化才会重启应用，所以需要设置 IDEA 的自动编译</li><li>设置自动配置 <code>File -&gt; Settings -&gt; Build -&gt; Complier -&gt; Build Project automatically</code></li><li><s>修改<code>Register</code>属性，执行快捷键<code>ctrl + shift + alt + /</code>，选择<code>Register</code>，勾上<code>Complier autoMake allow when app running</code></s></li><li><em>注意 IDEA 2021.2.3 版本中没有上面的选项</em>，迁移到了<code>File -&gt; Settings -&gt; Tools -&gt; Advanced Settings -&gt; Complier -&gt; Allow auto-make to start......</code></li></ul></li><li>配置完需要重启一下，然后有修改的话项目会自动更新，但是如果是自动触发的话，会造成频繁更新，对硬件有一定的负担，所以可以改成手动触发模式<ul><li>点击右上角 <code>Run/Debug Configurations</code></li><li>选择下拉 <code>Configuration -&gt; Spring Boot -&gt; Running Application Update Policies -&gt; On 'Update' action</code></li><li>选择 <code>Update classes and resources</code></li><li>如果有更新可以，使用快捷键 <code>Ctrl + F10</code> 重新编译</li></ul></li><li>快捷键<code>Ctrl + F9</code>，使用热部署重新启动</li></ol><h2 id="%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95" tabindex="-1">单元测试</h2><h3 id="%E4%BE%9D%E8%B5%96" tabindex="-1">依赖</h3><pre><code class="language-">&lt;!--单元测试--&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;</code></pre><h3 id="service%E4%B8%9A%E5%8A%A1%E5%B1%82%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E6%96%B9%E6%B3%95%E6%B5%8B%E8%AF%95" tabindex="-1">Service业务层————业务逻辑方法测试</h3><p><em>需要注意的是：</em></p><ol><li>如果在和<code>main文件夹</code>平级的<code>test文件夹</code>下新建了<code>java文件夹</code>，但是无法新建<code>java class</code>文件</li><li>那么就需要右键文件夹 <code>Mark Directory as -&gt; Test Sources Root</code>之后，文件夹变绿即可</li></ol><pre><code class="language-"># 示例代码package com.fx67ll.springboot.service;import com.fx67ll.springboot.Starter;import com.fx67ll.springboot.po.User;import com.fx67ll.springboot.query.UserQuery;import com.fx67ll.springboot.srevice.UserService;import com.github.pagehelper.PageInfo;import org.junit.After;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;/** * Service业务方法测试 * * Junit中的RunWith注解 表示该类是单元测试的执行类 * SpringRunner 是 spring-test 提供的测试执行单元类（是Spring单元测试中SpringJUnit4ClassRunner的新名字） * SpringBootTest注解 是执行测试程序的引导类 */@RunWith(SpringRunner.class)@SpringBootTest(classes = {Starter.class})public class TestUserService {    // 日志的使用    private Logger logger = LoggerFactory.getLogger(TestUserService.class);    @Resource    private UserService userService;    @Before    public void before() {        logger.info(&quot;单元测试开始......&quot;);    }    @Test    public void testQueryUserById() {        logger.info(&quot;测试根据用户id查询......&quot;);        User user = userService.queryUserById(1);        logger.info(&quot;用户记录: {}&quot;, user.toString());    }    @Test    public void testSelectUserListByParams() {        logger.info(&quot;测试根据分页条件查询用户列表......&quot;);        UserQuery userQuery = new UserQuery();        PageInfo&lt;User&gt; pageInfo = userService.selectUserListByParams(userQuery);        logger.info(pageInfo.toString());    }    @After    public void after() {        logger.info(&quot;单元测试结束......&quot;);    }}</code></pre><h3 id="controller%E6%8E%A7%E5%88%B6%E5%B1%82%E2%80%94%E2%80%94%E2%80%94%E2%80%94%E6%8E%A5%E5%8F%A3%E6%96%B9%E6%B3%95%E6%B5%8B%E8%AF%95" tabindex="-1">controller控制层————接口方法测试</h3><h4 id="%E4%BD%BF%E7%94%A8mockmvc%E8%BF%9B%E8%A1%8C%E6%B5%8B%E8%AF%95" tabindex="-1">使用MockMVC进行测试</h4><p><code>MockMvc</code>是由<code>spring-test</code>包提供，实现了对<code>Http请求</code>的模拟，能够直接使用网络的形式，转换到<code>Controller</code>的调用，使得测试速度快、不依赖网络环境。<br />同时提供了一套验证的工具，结果的验证十分方便</p><h4 id="%E4%BB%80%E4%B9%88%E6%98%AFmock" tabindex="-1">什么是Mock</h4><p>在面向对象的程序设计中，模拟对象<code>mock object</code>是以可控的方式模拟真实对象行为的假对象。<br />在编程过程中，通常通过模拟一些输入数据，来验证程序是否达到预期结果</p><h4 id="%E6%8E%A5%E5%8F%A3mockmvcbuilder" tabindex="-1">接口MockMvcBuilder</h4><p>提供一个唯一的<code>build方法</code>，用来构造<code>MockMvc</code>。<br />主要有两个实现：<code>StandaloneMockMvcBuilder</code>和<code>DefaultMockMvcBuilder</code>，分别对应两种测试方式，<br />即独立安装和集成Web环境测试（并不会集成真正的<code>web环境</code>，而是通过相应的<code>Mock API</code>进行模拟测试，无须启动服务器）。<br />MockMvcBuilders提供了对应的创建方法<code>standaloneSetup</code>方法和<code>webAppContextSetup</code>方法，在使用时直接调用即可。</p><pre><code class="language-"># 示例代码# PS：虽然提示测试通过，但是控制台一直没有打印出返回信息的记录，后期有空看看package com.fx67ll.springboot.controller;import com.fx67ll.springboot.Starter;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.http.MediaType;import org.springframework.mock.web.MockHttpServletResponse;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.test.web.servlet.MockMvc;import org.springframework.test.web.servlet.MvcResult;import org.springframework.test.web.servlet.ResultActions;import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;import org.springframework.test.web.servlet.result.MockMvcResultMatchers;@RunWith(SpringRunner.class)@SpringBootTest(classes = {Starter.class})@AutoConfigureMockMvcpublic class TestUserController {    // 日志的使用    private Logger logger = LoggerFactory.getLogger(TestUserController.class);    @Autowired    private MockMvc mockMvc;    /**     * 模拟测试用户列表查询     * 其实就在模拟真实环境下前端对后端发起的请求     */    @Test    public void apiTestSelectUserListByParams() throws Exception {        logger.info(&quot;开始模拟发送查询用户列表的请求......&quot;);        // 构建请求        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(&quot;/user/list&quot;)                .contentType(&quot;text/html&quot;) // 设置请求头信息                .accept(MediaType.APPLICATION_JSON); // 设置请求Accept头信息        // 发送请求        ResultActions perform = mockMvc.perform(requestBuilder);        // 校验请求结果        perform.andExpect(MockMvcResultMatchers.status().isOk());        // 获取执行完成后返回的结果        MvcResult mvcResult = perform.andReturn();        // 得到执行后的响应        MockHttpServletResponse response = mvcResult.getResponse();        // 打印结果        logger.info(String.valueOf(response.getContentLength()));        logger.info(&quot;响应状态: &quot;, response.getStatus());        logger.info(&quot;响应信息: &quot;, response.getContentAsString());        logger.info(&quot;结束模拟发送查询用户列表的请求......&quot;);    }    @Test    public void apiTestQueryUserByUsername() throws Exception {        logger.info(&quot;开始模拟根据用户名查询用户记录的请求......&quot;);        // 构建请求并发送        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(&quot;/user/name/admin&quot;))                .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();        // 打印结果        logger.info(&quot;响应状态: &quot;, mvcResult.getResponse().getStatus());        logger.info(&quot;响应信息: &quot;, mvcResult.getResponse().getContentAsString());        logger.info(&quot;结束模拟根据用户名查询用户记录的请求......&quot;);    }}</code></pre><h2 id="swagger2%E6%96%87%E6%A1%A3%E5%B7%A5%E5%85%B7" tabindex="-1">Swagger2文档工具</h2><h3 id="%E4%BE%9D%E8%B5%96-1" tabindex="-1">依赖</h3><p>在<code>pom.xml</code>中添加以下代码</p><pre><code class="language-">&lt;dependency&gt;&lt;groupId&gt;io.springfox&lt;/groupId&gt;&lt;artifactId&gt;springfox-swagger2&lt;/artifactId&gt;&lt;version&gt;2.9.2&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;&lt;groupId&gt;io.springfox&lt;/groupId&gt;&lt;artifactId&gt;springfox-swagger-ui&lt;/artifactId&gt;&lt;version&gt;2.9.2&lt;/version&gt;&lt;/dependency&gt;</code></pre><h3 id="%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3-1" tabindex="-1">常用注解</h3><p><strong>可以参考文章————<a href="https://blog.csdn.net/xiaojin21cen/article/details/78654652" target="_blank">swagger2 注解说明</a>详细学习，这里后期会补上说明</strong></p><h4 id="%40api" tabindex="-1">@Api</h4><p><em>主要是用在请求类上，用于说明该类的作用</em></p><pre><code class="language-"># 示例@Api(tags = &quot;xx模块&quot;)</code></pre><h4 id="%40apioperation" tabindex="-1">@ApiOperation</h4><p><em>主要是用在请求的方法上，说明方法的作用</em></p><pre><code class="language-"># 示例@ApiOperation(value = &quot;xx方法的作用&quot;, notes = &quot;xx方法的备注说明&quot;)</code></pre><h4 id="%40apiimplicitparams%E3%80%81%40apiimplicitparam" tabindex="-1">@ApiImplicitParams、@ApiImplicitParam</h4><p><em>主要是用在请求的方法上，说明方法的参数</em></p><pre><code class="language-"># 详细参数说明@ApiImplicitParams：用在请求的方法上，包含一组参数说明@ApiImplicitParam：对单个参数的说明        name：参数名    value：参数的说明、描述    required：参数是否必须必填    paramType：参数放在哪个地方        · query --&gt; 请求参数的获取：@RequestParam        · header --&gt; 请求参数的获取：@RequestHeader              · path（用于restful接口）--&gt; 请求参数的获取：@PathVariable        · body（请求体）--&gt;  @RequestBody User user        · form（普通表单提交）       dataType：参数类型，默认String，其它值dataType=&quot;Integer&quot;       defaultValue：参数的默认值# 单个参数示例@ApiImplicitParam(name = &quot;xxx&quot;, value = &quot;xxx&quot;, required = true, paramType = &quot;path&quot;, dataType = &quot;String&quot;, defaultValue = &quot;&quot;)# 多个参数示例@ApiImplicitParams({@ApiImplicitParam(name = &quot;xxxa&quot;, value = &quot;xxxa&quot;, required = true, paramType = &quot;body&quot;, dataType = &quot;String&quot;, defaultValue = &quot;&quot;),@ApiImplicitParam(name = &quot;xxxb&quot;, value = &quot;xxxb&quot;, required = true, paramType = &quot;body&quot;, dataType = &quot;String&quot;, defaultValue = &quot;&quot;),})</code></pre><h4 id="%40apiresponses%E3%80%81%40apiresponse" tabindex="-1">@ApiResponses、@ApiResponse</h4><p><em>主要是用在请求的方法上，说明错误响应的信息</em></p><pre><code class="language-"># 详细参数说明@ApiResponses：响应状态的说明。是个数组，可包含多个 @ApiResponse@ApiResponse：每个参数的说明    code：数字，例如400    message：信息，例如&quot;请求参数没填好&quot;    response：抛出异常的类# 多个参数示例，一般响应都是多个code，所以不写单个参数的示例了@ApiResponses({@ApiResponse(code = 200, message = &quot;请求成功&quot;),@ApiResponse(code = 578, message = &quot;请求参数错误&quot;),@ApiResponse(code = 404, message = &quot;请求路径没有或页面跳转路径不对&quot;)})</code></pre><h4 id="%40apimodel%E3%80%81%40apimodelproperty" tabindex="-1">@ApiModel、@ApiModelProperty</h4><ol><li>@ApiModel 经常用于请求的入参对象和响应返回值对象的描述<ul><li>入参是对象，即 @RequestBody 时， 用于封装请求（包括数据的各种校验）数据</li><li>返回值是对象，即 @ResponseBody 时，用于返回值对象的描述</li></ul></li><li>@ApiModelProperty 用于每个属性上面，说明属性的含义</li></ol><pre><code class="language-"># 示例@ApiModel(description = &quot;用户实体类&quot;)public class User {    @ApiModelProperty(value = &quot;用户名&quot;, required = true, example = &quot;0&quot;)    private Integer id;    @ApiModelProperty(value = &quot;用户ID&quot;, required = true, example = &quot;fx67ll&quot;)    private String userName;    @ApiModelProperty(value = &quot;用户密码&quot;, required = true, example = &quot;xxxxxxxx&quot;)    private String userPwd;}</code></pre><h2 id="%E5%88%86%E5%B8%83%E5%BC%8F%E7%BC%93%E5%AD%98%E5%B7%A5%E5%85%B7ehcache" tabindex="-1">分布式缓存工具Ehcache</h2><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFehcache" tabindex="-1">什么是Ehcache</h3><p><code>EhCache</code>是一个<code>纯Java</code>的进程内缓存框架，具有快速、精干等特点，是<code>Hibernate</code>中默认<code>CacheProvider</code>。<br /><code>Ehcache</code>是一种广泛使用的开源<code>Java分布式缓存</code>，主要面向通用缓存，<code>Java EE</code>和<code>轻量级容器</code>。<br />它具有内存和磁盘存储，缓存加载器，缓存扩展，缓存异常处理程序，一个<code>gzip</code>缓存<code>servlet</code>过滤器，支持<code>REST API</code>和<code>SOAP API</code>等特点。</p><h3 id="springcache%E7%9B%B8%E5%85%B3%E6%B3%A8%E8%A7%A3" tabindex="-1">SpringCache相关注解</h3><p>SpringBoot缓存实现内部使用SpringCache实现缓存控制，这里集成Ehcache实际上是对SpringCache抽象的一种实现<br /><strong>可以参考文章————<a href="https://www.jianshu.com/p/9d3c58ecf8ff" target="_blank">Spring Cache 简介</a>详细学习，这里后期会补上说明</strong></p><h4 id="%40enablecaching" tabindex="-1">@EnableCaching</h4><p>开启缓存功能，一般放在启动类上</p><h4 id="%40cacheconfig" tabindex="-1">@CacheConfig</h4><p>当我们需要缓存的地方越来越多，你可以使用<code>@CacheConfig(cacheNames = {&quot;cacheName&quot;})</code>注解在<code>Class</code>之上来统一指定<code>value</code>的值，<br />这时可省略<code>value</code>，如果你在你的方法依旧写上了<code>value</code>，那么依然以方法的<code>value</code>值为准</p><h4 id="%40cacheable" tabindex="-1">@Cacheable</h4><p>根据方法对其返回结果进行缓存，下次请求时，如果缓存存在，则直接读取缓存数据返回；如果缓存不存在，则执行方法，并把返回的结果存入缓存中，一般用在查询方法上<br /><em>注意<code>value</code>后面要使用<code>ehcache.xml</code>文件中所列的<code>cache.name</code></em></p><pre><code class="language-"># 单个参数示例代码@Cacheable(value = &quot;fx67llCache&quot;, key = &quot;#xxx&quot;)# 多个参数示例，采用拼接的方式@Cacheable(value = &quot;fx67llCache&quot;, key = &quot;#xxx.xxx + &#39;-&#39; + #xxx.xxx + &#39;-&#39; + #xxx.xxx&quot;)</code></pre><h4 id="%40cacheput" tabindex="-1">@CachePut</h4><p>使用该注解标志的方法，每次都会执行，并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据，而不需要再去查询数据库，一般用在新增方法上</p><pre><code class="language-"># 示例代码@CachePut(value = &quot;fx67llCache&quot;, key = &quot;#xxx.xxx&quot;)</code></pre><h4 id="%40cacheevict" tabindex="-1">@CacheEvict</h4><p>使用该注解标志的方法，会清空指定的缓存，一般用在更新或者删除方法上</p><pre><code class="language-"># 示例代码@CacheEvict(value = &quot;fx67llCache&quot;, key = &quot;#xxx&quot;)</code></pre><h4 id="%40caching" tabindex="-1">@Caching</h4><p>该注解可以实现同一个方法上同时使用多种注解</p><h3 id="ehcache%E7%9A%84%E4%BD%BF%E7%94%A8" tabindex="-1">Ehcache的使用</h3><ol><li>在<code>pom.xml</code>添加依赖<pre><code class="language-">&lt;!--Ehcache工具依赖--&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;&lt;dependency&gt;&lt;groupId&gt;net.sf.ehcache&lt;/groupId&gt;&lt;artifactId&gt;ehcache&lt;/artifactId&gt;&lt;/dependency&gt;</code></pre></li><li>添加<code>ehcache.xml</code>文件<pre><code class="language-">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;ehcache name=&quot;fx67llCache&quot;&gt;&lt;!--   diskStore：为缓存路径，ehcache分为内存和磁盘两级，此属性定义磁盘的缓存位置。参数解释如下：   user.home – 用户主目录   user.dir  – 用户当前工作目录   java.io.tmpdir – 默认临时文件路径 --&gt;&lt;diskStore path=&quot;D:\Java\test-ehcache-cache&quot;/&gt;&lt;!--   defaultCache：默认缓存策略，当ehcache找不到定义的缓存时，则使用这个缓存策略。只能定义一个。 --&gt;&lt;!--  name:缓存名称。  maxElementsInMemory:缓存最大数目  maxElementsOnDisk：硬盘最大缓存个数。  eternal:对象是否永久有效，一但设置了，timeout将不起作用。  overflowToDisk:是否保存到磁盘，当系统当机时  timeToIdleSeconds:设置对象在失效前的允许闲置时间（单位：秒）。仅当eternal=false对象不是永久有效时使用，可选属性，默认值是0，也就是可闲置时间无穷大。  timeToLiveSeconds:设置对象在失效前允许存活时间（单位：秒）。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用，默认是0.，也就是对象存活时间无穷大。  diskPersistent：是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.  diskSpoolBufferSizeMB：这个参数设置DiskStore（磁盘缓存）的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。  diskExpiryThreadIntervalSeconds：磁盘失效线程运行时间间隔，默认是120秒。  memoryStoreEvictionPolicy：当达到maxElementsInMemory限制时，Ehcache将会根据指定的策略去清理内存。默认策略是LRU（最近最少使用）。你可以设置为FIFO（先进先出）或是LFU（较少使用）。  clearOnFlush：内存数量最大时是否清除。  memoryStoreEvictionPolicy:可选策略有：LRU（最近最少使用，默认策略）、FIFO（先进先出）、LFU（最少访问次数）。   FIFO，first in first out，这个是大家最熟的，先进先出。   LFU， Less Frequently Used，就是上面例子中使用的策略，直白一点就是讲一直以来最少被使用的。如上面所讲，缓存的元素有一个hit属性，hit值最小的将会被清出缓存。   LRU，Least Recently Used，最近最少使用的，缓存的元素有一个时间戳，当缓存容量满了，而又需要腾出地方来缓存新的元素的时候，那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。   --&gt;&lt;defaultCachemaxElementsInMemory=&quot;10000&quot;eternal=&quot;false&quot;timeToIdleSeconds=&quot;120&quot;timeToLiveSeconds=&quot;120&quot;maxElementsOnDisk=&quot;10000000&quot;diskExpiryThreadIntervalSeconds=&quot;120&quot;memoryStoreEvictionPolicy=&quot;LRU&quot;/&gt;&lt;cachename=&quot;fx67llCache&quot;eternal=&quot;false&quot;maxElementsInMemory=&quot;100&quot;overflowToDisk=&quot;false&quot;diskPersistent=&quot;false&quot;timeToIdleSeconds=&quot;0&quot;timeToLiveSeconds=&quot;300&quot;memoryStoreEvictionPolicy=&quot;LRU&quot;/&gt;&lt;/ehcache&gt;</code></pre></li><li>在<code>application.yml</code>添加缓存配置<pre><code class="language-"># Ehcache 缓存配置cache:  ehcache:    config: classpath:ehcache.xml</code></pre></li><li>在入口类添加<code>@EnableCaching</code>注解，表示开启缓存</li><li>Java Bean 对象实现序列化，<code>public class User implements Serializable</code></li><li>在需要使用的地方使用现关注解，实现缓存可以减少从数据库查询的次数</li></ol><h2 id="%E5%AE%9A%E6%97%B6%E8%B0%83%E5%BA%A6%E5%B7%A5%E5%85%B7quartz" tabindex="-1">定时调度工具Quartz</h2><p><strong>可以参考文章————<a href="https://blog.csdn.net/yesirwu/article/details/97683166" target="_blank">Quartz定时调度</a>详细学习，这里后期会补上说明</strong></p><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFquartz" tabindex="-1">什么是Quartz</h3><p>在日常项目运行中，我们总会有需求在某一时间段周期性的执行某个动作，比如每天在某个时间段导出报表，或者每隔多久统计一次现在在线的用户量等。<br />在SpringBoot中有Java自带的<code>java.util.Timer</code>类，也可以在启动类添加<code>@EnableScheduling</code>注解引入定时任务环境</p><h3 id="quartz%E7%9A%84%E4%BD%BF%E7%94%A8" tabindex="-1">Quartz的使用</h3><ol><li>在<code>pom.xml</code>添加依赖<pre><code class="language-">&lt;!--Quartz工具依赖--&gt;&lt;dependency&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-starter-quartz&lt;/artifactId&gt;&lt;/dependency&gt;</code></pre></li><li>添加<code>job包</code>并编写<code>job任务</code>，实现<code>job接口</code>，并在<code>execute方法</code>中实现自己的业务逻辑<pre><code class="language-">package com.fx67ll.springboot.jobs;import org.quartz.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.text.SimpleDateFormat;import java.util.Date;public class TestQuartzJob implements Job {private Logger logger = LoggerFactory.getLogger(TestQuartzJob.class);@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {// 获取整理好的日期时间SimpleDateFormat simpleDateFormat = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);// 查询触发器名称和触发器属于哪个分组TriggerKey triggerKey = jobExecutionContext.getTrigger().getKey();//打印日志logger.info(&quot;当前触发器是: &quot; + triggerKey.getName() + &quot;，它所属的组别是: &quot; + triggerKey.getGroup() +&quot;----------触发时间: &quot; + simpleDateFormat.format(new Date()) +&quot;--&gt;&quot; + &quot;Hello fx67ll Spring Boot Quartz......&quot;);}}</code></pre></li><li>构建调度配置类，创建JobDetail实例并定义Trigger注册到scheduler，启动scheduler开启调度<pre><code class="language-">package com.fx67ll.springboot.conf;import com.fx67ll.springboot.jobs.TestQuartzJob;import org.quartz.*;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class QuartzCOnf {@Bean/** * 具体的可以被执行的调度程序 */public JobDetail jobDetailTestQuartz(){return JobBuilder.newJob(TestQuartzJob.class).storeDurably().build();}@Bean/** * 第一个测试触发器，主要是配置参数提示什么时候调用 * 应用场景有比如定时发送邮件之类的 */public Trigger triggerTestQuartzFirst(){SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule()// 每五秒执行一次.withIntervalInSeconds(1)// 永久重复，一直执行下去.repeatForever();return TriggerBuilder.newTrigger()// 设置触发器名称和分组.withIdentity(&quot;triggerTestQuartzFirst&quot;,&quot;groupTestQuartz&quot;).withSchedule(simpleScheduleBuilder).forJob(jobDetailTestQuartz()).build();}@Bean/** * 第二个测试触发器 */public Trigger triggerTestQuartzSecond(){return TriggerBuilder.newTrigger()// 设置触发器名称和分组.withIdentity(&quot;triggerTestQuartzSecond&quot;,&quot;groupTestQuartz&quot;)// 这里是通过定义表达式来表示每5秒执行一次，后续再深入研究下.withSchedule(CronScheduleBuilder.cronSchedule(&quot;0/5 * * * * ? *&quot;)).forJob(jobDetailTestQuartz()).build();}}</code></pre></li></ol><h2 id="%E9%99%84%E5%BD%95" tabindex="-1">附录</h2><h3 id="%E6%93%8D%E4%BD%9C%E4%BB%A3%E7%A0%81%E7%9B%AE%E5%BD%95%E8%AF%B4%E6%98%8E" tabindex="-1">操作代码目录说明</h3><table><thead><tr><th style="text-align:center">springboot-quickstart</th><th style="text-align:center">springboot-mybatis</th><th style="text-align:center">springboot-mybatis-crud</th><th style="text-align:center">springboot-mybatis-crud-prod</th></tr></thead><tbody><tr><td style="text-align:center">快速入门</td><td style="text-align:center">整合mybatis</td><td style="text-align:center">整套crud操作</td><td style="text-align:center">生产环境开发</td></tr></tbody></table><h4 id="%E6%93%8D%E4%BD%9C%E4%BB%A3%E7%A0%81%E8%B5%84%E6%BA%90%E5%9C%B0%E5%9D%80" tabindex="-1">操作代码资源地址</h4><ol><li><a href="https://github.com/fx67ll/fx67llBigData/tree/main/note/springboot/springboot_projects/springboot-quickstart" target="_blank">springboot-quickstart</a></li><li><a href="https://github.com/fx67ll/fx67llBigData/tree/main/note/springboot/springboot_projects/springboot-mybatis" target="_blank">springboot-mybatis</a></li><li><a href="https://github.com/fx67ll/fx67llBigData/tree/main/note/springboot/springboot_projects/springboot-mybatis-crud" target="_blank">springboot-mybatis-crud</a></li><li><a href="https://github.com/fx67ll/fx67llBigData/tree/main/note/springboot/springboot_projects/springboot-mybatis-crud-prod" target="_blank">springboot-mybatis-crud-prod</a></li></ol><h3 id="%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" tabindex="-1">参考资料</h3><ol><li><a href="https://www.bilibili.com/video/BV16i4y197zh" target="_blank">参考教程 ———— 两天搞定SpringBoot框架</a></li><li><a href="https://blog.csdn.net/weixin_41290863/article/details/111568023" target="_blank">参考文档 ———— JavaSpringBoot 中 @Autowired用法</a></li><li><a href="https://www.hangge.com/blog/cache/detail_2506.html" target="_blank">参考文档 ———— SpringBoot - @Configuration、@Bean注解的使用详解（配置类的实现）</a></li><li><a href="https://blog.csdn.net/the_ZED/article/details/105456946" target="_blank">参考文档 ———— 【Spring Boot】Spring基础 —— 组合注解与元注解</a></li><li><a href="https://blog.csdn.net/nimoyaoww/article/details/82999057" target="_blank">参考文档 ———— @RestController 和 @Controller 的区别</a></li><li><a href="https://blog.csdn.net/weixin_44093802/article/details/115601973" target="_blank">参考文档 ———— MapperScan注解详解</a></li><li><a href="https://blog.csdn.net/qq_36850813/article/details/80037363" target="_blank">参考文档 ———— Mapper.xml详解</a></li><li><a href="https://blog.csdn.net/qq_48508278/article/details/122648284" target="_blank">参考文档 ———— MVC三层架构（详解）</a></li><li><a href="https://www.cnblogs.com/charlottepl/p/14694865.html" target="_blank">参考文档 ———— 配置devtools热部署</a></li><li><a href="https://blog.csdn.net/IT_hejinrong/article/details/89155308" target="_blank">参考文档 ———— (十三)SpringBoot2.0热部署Devtools原理</a></li><li><a href="https://blog.csdn.net/qq_52978553/article/details/122376118" target="_blank">参考文档 ———— 2021版IDEA没有compiler.automake.allow.when.app.running</a></li><li><a href="https://blog.csdn.net/wo541075754/article/details/88983708" target="_blank">参考文档 ———— SpringBoot基础之MockMvc单元测试</a></li><li><a href="http://www.blogjava.net/libin2722/articles/406569.html" target="_blank">参考文档 ———— Ehcache详细解读</a></li><li><a href="https://blog.csdn.net/xiongzhichao/article/details/52349121" target="_blank">参考文档 ———— spring boot接入ehcache</a></li><li><a href="https://blog.csdn.net/mingyuli/article/details/120434810" target="_blank">参考文档 ———— SpringBoot(十二)： validation常用注解</a></li><li><a href="https://blog.csdn.net/weixin_49716609/article/details/116003488" target="_blank">参考文档 ———— SpringBoot之——Validator校验相关的注解</a></li><li><a href="https://blog.csdn.net/steven2xupt/article/details/87452664" target="_blank">参考文档 ———— 强悍的Spring之spring validation</a></li><li><a href="https://blog.csdn.net/weixin_39542850/article/details/111169911" target="_blank">json格式校验并显示错误_使用 Spring Validation 优雅地进行参数校验</a></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llBigData/blob/main/note/springboot/springboot-quickstart.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Atlas快速入门]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/atlas-quickstart" />
                <id>tag:https://fx67ll.xyz,2022-04-21:atlas-quickstart</id>
                <published>2022-04-21T16:34:12+08:00</published>
                <updated>2025-07-03T10:42:20+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h4 id="%E5%85%88%E8%AF%B4%E4%B8%80%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说一些废话</h4><p>之前的公司在数据中台的项目上调研决定启用了Atlas作为我们数据血缘管理的工具，让我给大家写了一份Atlas快速入门的文档，所以在这里我将这篇文档以一个纯新手视角的方式再一次优化，希望能够帮助大家更好的快速上手使用Atlas。</p><h2 id="%E4%BB%80%E4%B9%88%E6%98%AFatlas" tabindex="-1">什么是Atlas</h2><p>Atlas为组织提供了开放的元数据管理和治理功能，以建立其数据资产的目录，对这些资产进行分类和治理，并为数据科学家，分析师和数据治理团队提供围绕这些数据资产的协作功能。<br />而如果想要对这些数据做好管理，光用文字、文档等东西是不够的，必须用图，Atlas就是把元数据变成图的工具。</p><h3 id="%E5%85%83%E6%95%B0%E6%8D%AE%E7%AE%A1%E7%90%86%E5%BA%94%E8%AF%A5%E5%85%B7%E6%9C%89%E7%9A%84%E5%8A%9F%E8%83%BD" tabindex="-1">元数据管理应该具有的功能</h3><ol><li>搜索和发现：数据表、字段、标签、使用信息</li><li>访问控制：访问控制组、用户、策略</li><li>数据血缘：管道执行、查询</li><li>合规性：数据隐私、合规性注释类型的分类</li><li>数据管理：数据源配置、摄取配置、保留配置、数据清除策略</li><li>AI 可解释性、再现性：特征定义、模型定义、训练运行执行、问题陈述</li><li>数据操作：管道执行、处理的数据分区、数据统计</li><li>数据质量：数据质量规则定义、规则执行结果、数据统计</li></ol><h2 id="atlas%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E4%BA%A7%E7%94%9F%E7%9A%84%E4%BD%9C%E7%94%A8" tabindex="-1">Atlas在项目中产生的作用</h2><ol><li>数据血缘管理在数仓中的重要性非常高，他们可以帮助我们非常快速的查询到各种字段表之间的依赖关系</li><li>在我们知道数据血缘之后，如果发生一些突发状况，比如某两个表产生的指标出现了问题，就知道只需要重新跑这两个表就可以重新产出指标</li><li>也就是说，哪里出了问题哪里重跑我们的脚本文件即可，可以来说对我们的数据治理提供了非常大的帮助</li></ol><h2 id="%E7%89%B9%E6%80%A7" tabindex="-1">特性</h2><ol><li>Atlas支持各种Hadoop和非Hadoop元数据类型</li><li>提供了丰富的<code>REST API</code>进行集成</li><li>对数据血缘的追溯达到了字段级别，这种技术还没有其实类似框架可以实现</li><li>对权限也有很好的控制</li></ol><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFrest-api" tabindex="-1">什么是<code>REST API</code></h3><ul><li>REST API是一组关于如何构建Web应用程序API的架构规则、标准或指导，REST API遵循API原则的架构风格</li><li>REST是专门针对Web应用程序而设计的，其目的在于降低开发的复杂度，提高系统的可伸缩性</li></ul><h2 id="%E6%9E%B6%E6%9E%84" tabindex="-1">架构</h2><h3 id="%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E" tabindex="-1">架构说明</h3><p>说明元数据的存储是hbase，检索使用的索引solr，数据来源主要是hive和hbase，数据和atlas之间的通信使用的是kafka</p><pre><code class="language-">元数据存储：hbase，可以采集也可以导出  检索索引：solr数据来源：hive（重点），hbase（重点），sqoop，falcon，storm数据和atlas的通信通道：基于kafka的消息传递接口Admin UI：web界面操作Ranger Tag Based Policies：权限管理模块  Bussiness Type：业务分类  API：所有功能通过REST API向最终用户暴露，该API允许创建，更新和删除类型和实体  Graph Engine 图形引擎：使用Graph模型持久保存它管理的元数据对象  Type System 类型系统：用户为他们想要管理的元数据对象定义模型，Type System 称为实体和类型实例，表示受管理的实际元数据对象  </code></pre><h3 id="%E7%BB%84%E4%BB%B6%E4%BB%8B%E7%BB%8D" tabindex="-1">组件介绍</h3><ol><li>采用Hbase存储元数据</li><li>采用Solr实现索引</li><li>Ingest/Export 采集导出组件，Type System类型系统，Graph Engine图形引擎，共同构成Atlas的核心机制</li><li>所有功能通过API向用户提供，也可以通过Kafka消息系统进行集成</li><li>Atlas支持各种源获取元数据：Hive，Hbase，Sqoop，Storm</li><li>还有优秀的UI支持</li></ol><h2 id="%E5%AE%89%E8%A3%85%E5%92%8C%E4%BE%9D%E8%B5%96" tabindex="-1">安装和依赖</h2><h3 id="%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87" tabindex="-1">环境准备</h3><blockquote><p>jdk8、hadoop@2.7.2<br />zookeeper@3.4.10<br /><a href="mailto:kafka@0.11.0.2" target="_blank">kafka@0.11.0.2</a><br />hbase@1.3.1<br />solr@5.2.1<br />hive@1.2.1、mysql<br />azkaban@2.5.0<br />atlas@0.84</p></blockquote><h3 id="%E4%BE%9D%E8%B5%96%E9%9B%86%E6%88%90" tabindex="-1">依赖集成</h3><ol><li>Atlas集成外部Hbase，主要是通过修改以下配置文件来实现<ul><li>进入<code>/soft/atlas/conf/atlas-application.properties</code> -&gt; <code>atlas.graph.storage.hostname = 集群主机名</code>，修改atals存储数据主机</li><li>进入<code>/soft/atals/conf/hbase</code>，添加hbase集群的配置文件到<code>${Atlas_Home}</code>，示例命令<code>ln -s /soft/hbase/conf/ /soft/atlas/conf/hbase/</code>，相当于建立软连接</li><li>在<code>/soft/atlas/conf/atlas-env.sh</code>中添加<code>HBASE_CONF_DIR</code>，也就是Hbase配置文件路径<code>export HBASE_CONF_DIR = /soft/atlas/conf/hbase/conf</code></li></ul></li><li>Atlas集成外部Slor，参考视频<a href="https://www.bilibili.com/video/BV18y4y1r7vC?p=6" target="_blank">大数据技术之atlas视频教程</a></li><li>Atlas集成外部Kafka，参考视频<a href="https://www.bilibili.com/video/BV18y4y1r7vC?p=6" target="_blank">大数据技术之atlas视频教程</a></li><li>Atlas集成外部Hive，参考视频<a href="https://www.bilibili.com/video/BV18y4y1r7vC?p=6" target="_blank">大数据技术之atlas视频教程</a></li><li>Atlas修改 WEB UI 访问端口之类的，参考视频<a href="https://www.bilibili.com/video/BV18y4y1r7vC?p=6" target="_blank">大数据技术之atlas视频教程</a></li></ol><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFln--s%E5%91%BD%E4%BB%A4" tabindex="-1">什么是<code>ln -s</code>命令</h3><p>ln命令是linux系统中一个非常重要命令，英文全称是&quot;link&quot;，即链接的意思，它的功能是为某一个文件在另外一个位置建立一个同步的链接</p><h3 id="%E9%9B%86%E7%BE%A4%E5%90%AF%E5%8A%A8%E9%A1%BA%E5%BA%8F" tabindex="-1">集群启动顺序</h3><p><code>Hadoop -&gt; Zookeeper -&gt; Kafka -&gt; Hbase -&gt; Solr -&gt; 重启Atlas服务</code></p><h2 id="%E5%AF%BC%E5%85%A5hive%E5%85%83%E6%95%B0%E6%8D%AE" tabindex="-1">导入Hive元数据</h2><p>主要是通过执行<code>.sh</code>脚本文件来导入，后续可以引入<code>Azkaban来做全流程执行job</code></p><ol><li>生成数据</li><li>编写Azkaban程序运行job<ul><li>import.job</li><li>ods.job</li><li>dwd.job</li><li>dws.job</li><li>ads.job</li><li>export.job</li><li>将上述6个文件压缩成一个指标job的文件，例如<code>target-job.zip</code></li></ul></li><li>创建Azkaban工程并执行，后续就可以通过Atals动态查看各种血缘依赖关系，主要是图形化的界面</li></ol><h2 id="%E4%BD%BF%E7%94%A8-atlas-%E7%9A%84-rest-api-%E8%BF%9B%E8%A1%8C%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91" tabindex="-1">使用 Atlas 的 REST API 进行二次开发</h2><p>Atlas有丰富的开放接口供大家使用以进行二次开发，对于我们公司后续如果有非常重大的个性化需求项目的开发，能够提供很好的帮助，这里是<a href="https://atlas.apache.org/api/v2/index.html" target="_blank">官方的api说明文档地址</a></p><h2 id="web-ui-%E7%95%8C%E9%9D%A2%E4%B8%8A%E7%9A%84%E9%87%8D%E7%82%B9%E6%93%8D%E4%BD%9C%E8%AF%B4%E6%98%8E" tabindex="-1">WEB UI 界面上的重点操作说明</h2><h3 id="%E7%99%BB%E5%BD%95%E7%95%8C%E9%9D%A2(login)" tabindex="-1">登录界面(Login)</h3><p>主要会有公司配发的不同权限的使用账号和密码</p><h3 id="%E6%90%9C%E7%B4%A2%E7%95%8C%E9%9D%A2(search)" tabindex="-1">搜索界面(Search)</h3><p>在Search界面中提供了Basic(基础)和Advanced(高级)两种UI界面。<br />可查询的数据类型包括但不限于<code>hive_db、hive_table、hdfs_path</code>等等，点击橘色方框中的箭头即可查看选择可供查找的数据类型。</p><h3 id="%E5%9F%BA%E7%A1%80%E7%95%8C%E9%9D%A2(basic)" tabindex="-1">基础界面(Basic)</h3><p>在Basic界面中，可以进行更加细粒度的划分查询，还可以保存常用的查询条件组合。<br />包括的查询条件有：Type（查询的数据类型）、Classification（查询数据的所属分类）、Term（查询数据的术语）、Text（查询数据的name），<br />在Basic查询界面中还可以对<code>Type</code>和<code>ClassiFication</code>进行。</p><h3 id="%E9%AB%98%E7%BA%A7%E7%95%8C%E9%9D%A2(advanced)" tabindex="-1">高级界面(Advanced)</h3><p>在Advanced界面中，查询条件包括：Type（查询的数据类型）、Query（查询语句），也可以保存常用的查询条件组合</p><h3 id="%E5%88%86%E7%B1%BB%E7%95%8C%E9%9D%A2(classification)" tabindex="-1">分类界面(Classification)</h3><p>分类界面中可以进行分类标签的管理，在这个界面中可以新建一个分类，或者在一个分类下新建出一个小分类，也可以删除一个已有的分类，<br />通过分类的设定，并为数据添加相应的分类标签，可以方便数据的管理与查找。</p><h4 id="%E6%9F%A5%E7%9C%8B%E5%88%86%E7%B1%BB" tabindex="-1">查看分类</h4><p>点击相应的分类便可以看到拥有该分类和其子分类的数据，而在查询界面中点击数据中的分类标签，即可跳转到分类界面的相应分类下</p><h4 id="%E6%B7%BB%E5%8A%A0%E5%88%86%E7%B1%BB" tabindex="-1">添加分类</h4><p>在Atlas中一个数据可以拥有多个分类标签，在查询界面中，可以通过在数据的<code>Classification</code>列点击<code>+</code>为该数据添加分类</p><h4 id="%E5%88%86%E7%B1%BB%E4%BC%A0%E6%92%AD%E9%80%89%E9%A1%B9(propagate)" tabindex="-1">分类传播选项(propagate)</h4><p>分类传播使数据所关联的分类能延血缘关系，使其这个数据的后代也能得到其父类数据的分类标签。<br />为第一个数据打上分类标签后其数据的子类也得到了该分类的标签，<br />更改第一个数据的分类标签，子类的分类标签也会受到更改，删除第一个数据的分类标签，其子类对应的分类标签也会删除。<br /><strong>总结来说数据的分类标签能沿着血缘关系传播到其子类</strong></p><ol><li>情况一：删除其父类实体时，子类所得到的父类的标签会被删除</li><li>情况二：当子类到父类之间的血缘关系被破坏时，子类也会失去父类的分类标签</li><li>情况三：即使子类到父类之间的一条血缘链被破坏，只要还有另一条血缘链存在且能使子类连接到父类，那么子类还能保存其父类的分类标签</li></ol><h3 id="%E6%9C%AF%E8%AF%AD%E8%A1%A8%E7%95%8C%E9%9D%A2(glossary)%EF%BC%8C%E4%B9%9F%E5%B0%B1%E6%98%AF%E8%AF%8D%E6%B1%87" tabindex="-1">术语表界面(Glossary)，也就是词汇</h3><p>在Atlas的Glossary界面下，可以创建术语表，并且在术语表中可以创建一些&quot;单词&quot;，这些&quot;单词&quot;能彼此进行关联和分类。<br />以便业务用户在使用的时候，即使这些&quot;单词&quot;在不同的术语表中也能很好的理解它们。此外，这些术语也是可以映射到数据资产中的，即可以给数据添加术语标签。<br />术语表抽象出了和数据相关的专业术语，使得用户能以他们更熟悉的方式去查找和使用数据。<br /><em>需要注意的是如果为数据添加术语时，该术语已经设置了分类标签，那么这个术语的分类标签会被数据继承：</em><br />即数据的分类标签中，会被加上术语的分类标签，这样做是因为，我们可以为术语单独创建一个分类，然后为数据打上术语标签时，数据也会继承这个分类。<br />然后我们通过查看这个分类的数据时就能找到被打上这个术语标签的所有数据及术语本身。</p><h4 id="%E6%9C%AF%E8%AF%AD%E8%A7%86%E5%9B%BE%E5%85%81%E8%AE%B8%E7%94%A8%E6%88%B7%E6%89%A7%E8%A1%8C%E4%BB%A5%E4%B8%8B%E6%9C%AF%E8%AF%AD%E6%93%8D%E4%BD%9C" tabindex="-1">术语视图允许用户执行以下术语操作</h4><ol><li>创建，更新和删除术语</li><li>添加，删除和更新与术语关联的分类</li><li>添加，删除和更新术语的分类</li><li>在术语之间创建各种关系</li><li>查看与术语关联的实体</li></ol><h4 id="%E7%B1%BB%E5%88%AB%E8%A7%86%E5%9B%BE%E5%85%81%E8%AE%B8%E7%94%A8%E6%88%B7%E6%89%A7%E8%A1%8C%E4%BB%A5%E4%B8%8B%E6%9C%AF%E8%AF%AD%E6%93%8D%E4%BD%9C" tabindex="-1">类别视图允许用户执行以下术语操作</h4><ol><li>创建，更新和删除类别和子类别</li><li>将术语与类别相关联</li><li>Category的作用就是方便术语的管理与使用</li></ol><h3 id="%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86" tabindex="-1">如何进行权限管理</h3><h4 id="%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E6%A8%A1%E5%BC%8F%E9%85%8D%E7%BD%AE%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86%EF%BC%8C%E4%B9%9F%E6%98%AF%E6%88%91%E4%BB%AC%E5%85%AC%E5%8F%B8%E4%BD%BF%E7%94%A8%E7%9A%84%E6%A8%A1%E5%BC%8F" tabindex="-1">配置文件模式配置权限管理，也是我们公司使用的模式</h4><ol><li>启用<code>Atlas Simple Authprizer</code> 权限管理 需要在<code>/atlas/conf/</code> 中配置文件 atlas-application.properties`</li></ol><pre><code class="language-">atlas.authorizer.impl = simpleatlas.authorizer.simple.authz.policy.file = /atlas/conf/atlas-simple-authz-policy.json</code></pre><ol start="2"><li>在<code>/atlas/conf</code>目录中有两个文件<code>atlas-simple-authz-policy.json</code>和<code>users-credentials.properties</code><ul><li><code>atlas-simple-authz-policy.json</code> 文件用于配置权限、用户权限、用户组权限</li><li><code>users-credentials.properties</code> 用于配置用户名、用户所属用户组、用户密码，<em>需要注意的是：密码采用sha256编码</em></li></ul></li></ol><h4 id="%E4%BD%BF%E7%94%A8%E5%A4%96%E9%83%A8%E6%8F%92%E4%BB%B6%E5%AE%9E%E7%8E%B0%E5%8F%AF%E8%A7%86%E5%8C%96%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86" tabindex="-1">使用外部插件实现可视化权限管理</h4><ol><li>使用ranger可以实现一个可视化的Atlas权限的管理，需要在<code>/atlas/conf</code>目录中配置<code>atlas-application.properties</code></li></ol><pre><code class="language-">atlas.authorizer.impl = ranger</code></pre><ol start="2"><li>Atlas Ranger Authorizer支持三种资源层次结构，以控制对类型，实体和管理操作的访问</li><li>Ranger会生成审核日志，包含用户访问的详细信息</li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llBigData/blob/main/note/atlas/atlas-use.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Hive面试大纲 🕹️0.2.3]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/interview-hive-list" />
                <id>tag:https://fx67ll.xyz,2022-03-26:interview-hive-list</id>
                <published>2022-03-26T13:46:28+08:00</published>
                <updated>2025-07-03T10:42:25+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h4 id="%E6%96%B9%E4%BE%BF%E8%87%AA%E5%B7%B1%E7%B3%BB%E7%BB%9F%E6%80%A7%E5%9B%9E%E5%BF%86%EF%BC%8C%E6%A0%B9%E6%8D%AE*%E7%9A%84%E6%95%B0%E9%87%8F%E6%9D%A5%E6%A0%87%E8%AE%B0%E9%87%8D%E8%A6%81%E6%80%A7" tabindex="-1">方便自己系统性回忆，根据<code>*</code>的数量来标记重要性</h4><blockquote><p><code>*</code> 简单了解<br /><code>**</code> 熟悉掌握<br /><code>***</code> 需要精通</p></blockquote><ol><li>Hive的介绍（*）<ul><li>Hive和Hadoop的关系</li><li>Hive的特点</li><li>Hive的缺点</li><li>Hive常见的应用场景</li><li>Hive和mysql的区别</li></ul></li><li>Hive的架构（*）</li><li>Hive的数据（**）<ul><li>Hive的数据模型</li><li>Hive的底层如何存储Null值</li><li>Hive中元数据<code>metadata</code>和元数据商店<code>metastore</code>)`的作用</li><li>Hive有哪些保存元数据<code>metadata</code>的方式</li><li>Hive元数据存储方式中，本地模式和远程模式的区别</li><li>Hive的数据类型</li><li>Hive的隐式类型转换规则</li><li>Hive数据存储所使用的文件格式</li><li>Hive中使用的压缩算法</li><li>什么是数据可分割</li><li>关于压缩模式说明</li></ul></li><li>Hive的安装与使用（*）<ul><li>如何在Hive中集成HBase</li><li>如何通过 HiveSQL 来直接读写 HBase</li></ul></li><li>Hive的分区和分桶（**）<ul><li>什么是Hive分区</li><li>Hive分区的优点</li><li>Hive分区的缺点</li><li>什么是Hive分桶</li><li>关于Hive索引的说明</li><li>Hive分桶的优点</li><li>Hive分桶的缺点</li><li>Hive中静态分区和动态分区的区别</li><li>Hive动态分区的参数设定</li></ul></li><li>Hive的内部表和外部表（*）<ul><li>什么是Hive的内部表和外部表</li><li>Hive内部表和外部表的区别是什么</li><li>生产环境中为什么建议使用外部表</li></ul></li><li>Hive SQL（***）<ul><li>Hive中的SQL如何转化成MapReduce的</li><li>Hive中如何查询A表中B表不存在的数据</li><li>Hive中有哪些连接查询以及如何使用</li><li>Hive中左连接和内连接的区别</li><li>Hive中左连接的底层原理</li><li>Hive查询时候 ON 和 WHERE 有什么区别</li></ul></li><li>Hive 函数（***）<ul><li>如何使用UDF/UDAF/UDTF（<em>待完善</em>）</li><li>为什么使用UDF/UDAF/UDTF（<em>待完善</em>）</li><li>你写过什么样的UDF/UDAF/UDT（<em>待完善</em>）</li><li>Hive自定义函数实现了什么函数什么接口 （<em>待完善</em>）</li><li>Hive中如何去重</li><li>Hive中排序函数的使用方式及区别</li><li>其他待补充说明的函数（<em>待完善</em>）</li></ul></li><li>Hive 运维（*）<ul><li>如何监控一个提交后的Hive状态</li></ul></li><li>Hive 优化（***）<ul><li>暂时没完成（<em>待完善</em>）</li></ul></li></ol>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Hive面试宝典 🕹️0.2.3]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/interview-hive" />
                <id>tag:https://fx67ll.xyz,2022-03-26:interview-hive</id>
                <published>2022-03-26T13:40:15+08:00</published>
                <updated>2025-07-03T10:42:34+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h4 id="%E5%85%88%E8%AF%B4%E4%B8%80%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说一些废话</h4><p>总结一下Hive面试宝典，方便读者快速过一遍Hive面试所需要的知识点</p><h2 id="hive%E7%9A%84%E4%BB%8B%E7%BB%8D" tabindex="-1">Hive的介绍</h2><h3 id="hive%E5%92%8Chadoop%E7%9A%84%E5%85%B3%E7%B3%BB" tabindex="-1">Hive和Hadoop的关系</h3><ol><li>Hive利用hdfs存储数据，利用MapReduce查询数据</li><li>Hive的数据存储在hdfs上，简单的说Hive就是hdfs的简单一种映射，比如：Hive的一张表映射hdfs上的一个文件，Hive的一个数据库就映射为hdfs上的文件夹</li><li>Hive是一个计算框架，他是MapReduce的一种封装，实际上他的底层还是MR，Hive就是用人们熟悉的sql对数据进行分析的</li><li>Hive执行程序是运行在Yarn上的</li></ol><h3 id="hive%E7%9A%84%E7%89%B9%E7%82%B9" tabindex="-1">Hive的特点</h3><ol><li>Hive可以自由的扩展集群的规模，一般情况下不需要重启服务（世界上最大的Hadoop集群在Yahoo!，2009年的规模在4000台节点左右）</li><li>Hive支持用户自定义函数，用户可以根据自己的需求来实现自己的函数（可能会引申自定义函数）</li><li>良好的容错性，节点出现问题SQL仍可完成执行（可能会拓展数据倾斜相关问题，或者直接问你你工作中有没有遇到这样的问题）</li></ol><h3 id="hive%E7%9A%84%E7%BC%BA%E7%82%B9" tabindex="-1">Hive的缺点</h3><ol><li>Hive的HQL表达能力有限。迭代式算法无法表达；数据挖掘方面不擅长</li><li>Hive的效率比较低。Hive自动生成的MapReduce作业，通常情况下不够智能化；Hive调优比较困难，粒度较粗</li><li>Hive执行延迟<ul><li>Hive 在查询数据的时候，由于没有索引，需要扫描整个表，因此延迟较高</li><li>另外一个导致 Hive 执行延迟高的因素是 MapReduce框架，由于MapReduce 本身具有较高的延迟，因此在利用MapReduce 执行Hive查询时，也会有较高的延迟</li><li>相对的，数据库的执行延迟较低。当然，这个低是有条件的，即数据规模较小，当数据规模大到超过数据库的处理能力的时候，Hive的并行计算显然能体现出优势</li></ul></li></ol><h3 id="hive%E5%B8%B8%E8%A7%81%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF" tabindex="-1">Hive常见的应用场景</h3><ol><li>日志分析：大部分互联网公司使用Hive进行日志分析，包括百度、淘宝等<ul><li>统计网站一个时间段内的pv、uv</li><li>多维度数据分析</li></ul></li><li>海量结构化数据离线分析</li></ol><h3 id="hive%E5%92%8Cmysql%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">Hive和mysql的区别</h3><ol><li>Hive采用了类SQL的查询语言HQL（hive query language），除了HQL之外，其余无任何相似的地方，Hive是为了数据仓库设计的</li><li>存储位置：Hive在Hadoop上；mysql将数据存储在设备或本地系统中</li><li>数据更新：Hive不支持数据的改写和添加，是在加载的时候就已经确定好了；数据库可以CRUD</li><li>索引：Hive无索引，每次扫描所有数据，底层是MR，并行计算，适用于大数据量；mysql有索引，适合在线查询数据</li><li>执行：Hive底层是MarReduce；mysql底层是执行引擎</li><li>可扩展性：Hive：大数据量，慢慢扩去吧；mysql:相对就很少了</li></ol><h2 id="hive%E7%9A%84%E6%9E%B6%E6%9E%84" tabindex="-1">Hive的架构</h2><pre><code class="language-"># Hive架构简易示意Meta Store -&gt; Client (CLI/JDBC/WebGUI + Driver/驱动 + SQL Parser/解析器 + Physical Plan/编译器 + QueryOptimizer/优化器 + Execution/执行器) -&gt;MapReduce -&gt;HDFS</code></pre><ol><li>用户接口：Hive 对外提供了三种服务模式，即 Hive 命令行模式（CLI），Hive 的 Web 模式（WUI），Hive 的远程服务（Client）<ul><li>其中最常用的是 CLI shell 命令行，CLI 启动的时候，会同时启动一个Hive副本</li><li>WUI 是通过浏览器访问 Hive，默认端口是<em><strong>9999</strong></em></li><li>Client 是Hive的客户端，，在启动 Client模式 的时候，需要指出 Hive Server 所在节点，并且在该节点启动 Hive Server</li><li>JDBC/ODBC用 JAVA 实现，与传统数据库 JDBC 类似</li></ul></li><li>元数据存储：通常是存储在关系数据库如 mysql , derby中<ul><li>Hive中的元数据包括表的名字，表的列和分区及其属性，表的属性（是否为外部表等），表的数据所在目录等</li></ul></li><li>解释器、编译器、优化器、执行器<ul><li>解释器、编译器、优化器完成 HQL 查询语句从词法分析、语法分析、编译、优化以及查询计划的生成</li><li>生成的查询计划存储在 HDFS 中，并在随后有 MapReduce 调用执行（注意！！！包含*的查询，比如select * from tbl不会生成MapRedcue任务）</li><li>===============================================================</li><li>解析器（parser）：将查询字符串转化为解析树表达式</li><li>===============================================================</li><li>编译器（physical plan）：分为<code>语义分析器（semantic analyzer）</code>和 <code>逻辑策略生成器（logical plan generator）</code></li><li>语义分析器（semantic analyzer）：将解析树表达式转换为基于块（block-based）的内部查询表达式</li><li>逻辑策略生成器（logical plan generator）：将内部查询表达式转换为逻辑策略，这些策略由逻辑操作树组成</li><li>===============================================================</li><li>优化器（optimizer）：通过逻辑策略构造多途径并以不同方式重写</li></ul></li></ol><h2 id="hive%E7%9A%84%E6%95%B0%E6%8D%AE" tabindex="-1">Hive的数据</h2><h3 id="hive%E7%9A%84%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B" tabindex="-1">Hive的数据模型</h3><ol><li>Hive中所有的数据都存储在hdfs中，没有专门的数据存储格式（可支持TextFile，SequenceFile，ParquetFile，RCFILE等）</li><li>只需要在创建表的时候告诉Hive数据中的列分隔符和行分隔符，Hive就可以解析数据</li><li>Hive中包含以下数据模型：DB、Table、External Table、Partition、Bucket<ul><li>DB：在hdfs中表现为<code>${hive.metastore.warehouse.dir}</code>目录下一个文件夹</li><li>Table：在hdfs中表现所属db目录下一个文件夹，普通表删除表后，hdfs上的文件都删了</li><li>External Table：外部表, 与table类似，不过其数据存放位置可以在任意指定路径，外部表删除后，hdfs上的文件没有删除，只是把文件删除了</li><li>Partition：在hdfs中表现为table目录下的子目录</li><li>Bucket：桶在hdfs中表现为同一个表目录下根据hash散列之后的多个文件，会根据不同的文件把数据放到不同的文件中</li></ul></li></ol><h3 id="hive%E7%9A%84%E5%BA%95%E5%B1%82%E5%A6%82%E4%BD%95%E5%AD%98%E5%82%A8null%E5%80%BC" tabindex="-1">Hive的底层如何存储Null值</h3><ol><li>Null在Hive底层默认是用’\N’来存储的</li><li>能够经过<code>alter table test SET SERDEPROPERTIES('serialization.null.format' = 'a');</code>来修改</li></ol><h3 id="hive%E4%B8%AD%E5%85%83%E6%95%B0%E6%8D%AEmetadata%E5%92%8C%E5%85%83%E6%95%B0%E6%8D%AE%E5%95%86%E5%BA%97metastore%E7%9A%84%E4%BD%9C%E7%94%A8" tabindex="-1">Hive中元数据<code>metadata</code>和元数据商店<code>metastore</code>的作用</h3><ol><li>metadata即元数据，元数据包含用Hive创建的database、tabel等的元信息，元数据存储在<code>关系型数据库(RDBMS)</code>中，如derby、mysql等</li><li>metastore的作用是：客户端连接metastore服务，metastore再去连接mysql数据库来存取元数据，<br />有了metastore服务，就可以有多个客户端同时连接，而且这些客户端不需要知道mysql数据库的用户名和密码，只需要连接metastore服务即可</li></ol><h3 id="hive%E6%9C%89%E5%93%AA%E4%BA%9B%E4%BF%9D%E5%AD%98%E5%85%83%E6%95%B0%E6%8D%AEmetadata%E7%9A%84%E6%96%B9%E5%BC%8F" tabindex="-1">Hive有哪些保存元数据<code>metadata</code>的方式</h3><ol><li>内嵌模式：将元数据保存在本地内嵌的derby数据库中，内嵌的derby数据库每次只能访问一个数据文件，也就意味着它不支持多会话连接，适用于用来实验，不适用于生产环境</li><li>本地模式：将元数据保存在本地独立的数据库中（一般是mysql），这可以支持多会话连接</li><li>远程模式：把元数据保存在远程独立的mysql数据库中，避免每个客户端都去安装mysql数据库</li><li>需要注意的是： 内存数据库derby，安装小，但是数据存在内存，不稳定。mysql数据库，数据存储模式可以自己设置，持久化好，查看方便</li></ol><h3 id="hive%E5%85%83%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F%E4%B8%AD%EF%BC%8C%E6%9C%AC%E5%9C%B0%E6%A8%A1%E5%BC%8F%E5%92%8C%E8%BF%9C%E7%A8%8B%E6%A8%A1%E5%BC%8F%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">Hive元数据存储方式中，本地模式和远程模式的区别</h3><ol><li><strong>本地元存储</strong>和<strong>远程元存储</strong>都采用<strong>外部数据库</strong>来存储元数据</li><li><strong>本地元存储</strong>不需要单独起metastore服务，用的是跟Hive在同一个进程里的metastore服务</li><li><strong>远程元存储</strong>需要单独起metastore服务，然后每个客户端都在配置文件里配置连接到该metastore服务，远程元存储的metastore服务和Hive运行在不同的进程</li></ol><h3 id="hive%E7%9A%84%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B" tabindex="-1">Hive的数据类型</h3><ol><li>基本数据类型，因为Hive的底层是用java开发，所以基本数据类型和java保持一致<ul><li>整型 tinyint(字节整型) / smallint(短整型) / int(整型) / bigint(长整型)，分别占用1/2/4/8个字节，等价于java的 byte/short/int/long</li><li>浮点型 float(浮点型) / double(双精度浮点型)，分别占用4/8个字节，等价于java的 float/double</li><li>字符型 string，等价于数据库的 varchar，可变字符串，理论上可以存储2GB的字节</li><li>布尔型 boolean，等价于java的 boolean</li></ul></li><li>复杂数据类型<ul><li>array/map，等价于java的array/map</li><li>struct，等价于c语言中的struct</li></ul></li><li>类型转换<ul><li>Hive 的原子数据类型是可以进行隐式转换的，类似于 Java 的类型转换</li><li>例如某表达式使用 int 类型，tinyint 会自动转换为 int 类型</li><li>但是 Hive 不会进行反向转化，例如，某表达式使用 tinyint 类型，int 不会自动转换为 tinyint 类型，它会返回错误，除非使用 CAST 操作</li><li>===============================================================</li><li>可以使用 CAST 操作显示进行数据类型转换</li><li>例如 CAST(‘1’ AS INT) 将把字符串’1’ 转换成整数 1</li><li>如果强制类型转换失败，如执行 CAST(‘X’ AS INT)，表达式返回空值 NULL</li></ul></li></ol><h3 id="hive%E7%9A%84%E9%9A%90%E5%BC%8F%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E8%A7%84%E5%88%99" tabindex="-1">Hive的隐式类型转换规则</h3><ol><li>任何整数类型都可以隐式地转换为一个范围更广的类型，如 tinyint 可以转换成 int，int 可以转换成 bigint</li><li>所有整数类型、float 和 string 类型都可以隐式地转换成 double</li><li>tinyint、smallint、int 都可以转换为 float</li><li>boolean 类型不可以转换为任何其它的类型</li></ol><h3 id="hive%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E6%89%80%E4%BD%BF%E7%94%A8%E7%9A%84%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F" tabindex="-1">Hive数据存储所使用的文件格式</h3><ol><li>默认是TextFile文件格式<ul><li>文本格式，Hive的默认格式，数据不压缩，磁盘开销大、数据解析开销大</li><li>对应的Hive API为：<code>org.apache.hadoop.mapred.TextInputFormat和org.apache.hive.ql.io.HiveIgnoreKeyTextOutputFormat;</code></li><li>可结合Gzip、Bzip2使用(系统自动检查，执行查询时自动解压)，但是使用这种方式，hive不会对数据进行切分，从而无法对数据进行并行操作</li></ul></li><li>RCFile文件格式<ul><li>RCFile是一种行列存储相结合的存储方式，先将数据按行进行分块再按列式存储，保证同一条记录在一个块上，避免读取多个块，有利于数据压缩和快速进行列存储</li><li>对应Hive API为：<code>org.apache.hadoop.hive.ql.io.RCFileInputFormat和org.apache.hadoop.hive.ql.io.RCFileOutputFormat;</code></li></ul></li><li>ORCFile文件格式<ul><li>数据按行分块，每块按照列存储，不是真正意义上的列存储，可以理解为分段列存储</li><li>用于降低Hadoop数据存储空间和加速Hive查询速度</li><li>ORCfile特点是压缩比比较高，压缩快，快速列存取，是RCfile的改良版本，相比RCfile能够更好的压缩，更快的查询</li><li>需要注意的是ORC在读写时候需要消耗额外的CPU资源来压缩和解压缩，当然这部分的CPU消耗是非常少的</li><li>优点：</li></ul><pre><code class="language-">每个task只输出单个文件，减少namenode负载；支持各种复杂的数据类型，比如：datetime，decima以及复杂类型struct、list、map；文件中存储了一些轻量级的索引数据；基于数据类型的块模式压缩：integer类型的列用行程长度编码，string类型的列使用字典编码；用多个相互独立的recordReaders并行读相同的文件无需扫描markers即可分割文件绑定读写所需内存metadata存储用protocol buffers，支持添加和删除列</code></pre></li><li>SequenceFile文件格式<ul><li>Hadoop提供的二进制文件，Hadoop支持的标准文件</li><li>数据直接序列化到文件中，SequenceFile文件不能直接查看，可以通过Hadoop fs -text查看</li><li>SequenceFile具有使用方便、可分割、可压缩、可进行切片，压缩支持NONE、RECORD、BLOCK（优先）</li><li>对应Hive API：<code>org.apache.hadoop.mapred.SequenceFileInputFormat和org.apache.hadoop.hive.ql.io.HiveSequenceFileOutputFormat;</code></li></ul></li><li>Parquet文件格式<ul><li>二进制存储，面向分析性的存储格式</li><li>能够很好的压缩，同时减少大量的表扫描和反序列化的时间，有很好的查询性能，支持有限的模式演进，但是写速度通常比较慢</li><li>Parquet文件是以二进制方式存储的，所以是不可以直接读取的，文件中包括该文件的数据和元数据，因此Parquet格式文件是自解析的</li></ul></li><li>总结<ul><li>TextFile 存储空间消耗比较大，并且压缩的text无法分割和合并查询的效率最低，可以直接存储，加载数据的速度最高</li><li>SequenceFile 存储空间消耗最大，压缩的文件可以分割和合并，查询效率高</li><li>ORCFile / RCFile 存储空间最小，查询的效率最高，加载的速度最低</li><li>Parquet 格式是列式存储，有很好的压缩性能和表扫描功能</li><li>SequenceFile / ORCFile / RCFile 格式的表不能直接从本地文件导入数据，数据要先导入到TextFile格式的表中，<br />然后再从TextFile表中导入到SequenceFile/ORCFile/RCFile表中</li></ul></li></ol><h3 id="hive%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84%E5%8E%8B%E7%BC%A9%E7%AE%97%E6%B3%95" tabindex="-1">Hive中使用的压缩算法</h3><ol><li>我们原始数据使用的是LZO的压缩格式，因为原始数据比较大，所以选择了支持切割的LZO压缩</li><li>清洗过的数据存到DWD层，我们在DWS中需要对清洗后的数据进行分析，所以我们DWD层使用的存储格式是Parquet，压缩格式是Snappy</li><li>之前我们压缩还遇到过一个问题，当时之前的项目中使用的是Snappy+ORC存储，后来我发现使用Snappy+ORC 存储比ORC单独存储还多占用了近一半的空间</li><li>后来我又对各个压缩格式及存储格式的结合做了一个测试，最终单独使用ORC存储节省了大量的空间</li><li>Snappy压缩格式<ul><li>其中压缩比bzip2 &gt; zlib &gt; gzip &gt; deflate &gt; snappy &gt; lzo &gt; lz4，在不同的测试场景中，会有差异，这仅仅是一个大概的排名情况</li><li>bzip2、zlib、gzip、deflate可以保证最小的压缩，但在运算中过于消耗时间</li><li>从压缩性能上来看：lz4 &gt; lzo &gt; snappy &gt; deflate &gt; gzip &gt; bzip2，其中lz4、lzo、snappy压缩和解压缩速度快，压缩比低</li><li>所以一般在生产环境中，经常会采用lz4、lzo、snappy压缩，以保证运算效率</li></ul></li></ol><h3 id="%E4%BB%80%E4%B9%88%E6%98%AF%E6%95%B0%E6%8D%AE%E5%8F%AF%E5%88%86%E5%89%B2" tabindex="-1">什么是数据可分割</h3><ol><li><p>在考虑如何压缩那些将由MapReduce处理的数据时，考虑压缩格式是否支持分割是很重要的。<br />考虑存储在HDFS中的未压缩的文件，其大小为1GB，HDFS的块大小为64MB，所以该文件将被存储为16块。<br />将此文件用作输入的MapReduce作业会创建1个输人分片（split，也称为“分块”。对于block，我们统一称为“块”。）<br />每个分片都被作为一个独立map任务的输入单独进行处理</p></li><li><p>现在假设，该文件是一个gzip格式的压缩文件，压缩后的大小为1GB。和前面一样，HDFS将此文件存储为16块。<br />然而，针对每一块创建一个分块是没有用的，因为不可能从gzip数据流中的任意点开始读取，map任务也不可能独立于其他分块只读取一个分块中的数据。<br />gzip格式使用DEFLATE来存储压缩过的数据，DEFLATE将数据作为一系列压缩过的块进行存储。<br />问题是，每块的开始没有指定用户在数据流中任意点定位到下一个块的起始位置，而是其自身与数据流同步。<br />因此，gzip不支持分割(块)机制。</p></li><li><p>在这种情况下，MapReduce不分割gzip格式的文件，因为它知道输入是gzip压缩格式的(通过文件扩展名得知)，而gzip压缩机制不支持分割机制。<br />因此一个map任务将处理16个HDFS块，且大都不是map的本地数据。<br />与此同时，因为map任务少，所以作业分割的粒度不够细，从而导致运行时间变长。</p></li></ol><h3 id="%E5%85%B3%E4%BA%8E%E5%8E%8B%E7%BC%A9%E6%A8%A1%E5%BC%8F%E8%AF%B4%E6%98%8E" tabindex="-1">关于压缩模式说明</h3><ol><li>压缩模式评价：<pre><code class="language-">可使用以下三种标准对压缩方式进行评价：压缩比：压缩比越高，压缩后文件越小，所以压缩比越高越好。压缩时间：越快越好。已经压缩的格式文件是否可以再分割：可以分割的格式允许单一文件由多个Mapper程序处理，可以更好的并行化。</code></pre></li><li>压缩模式对比<pre><code class="language-">BZip2有最高的压缩比但也会带来更高的CPU开销，Gzip较BZip2次之。如果基于磁盘利用率和I/O考虑，这两个压缩算法都是比较有吸引力的算法。LZO和Snappy算法有更快的解压缩速度，如果更关注压缩、解压速度，它们都是不错的选择。 LZO和Snappy在压缩数据上的速度大致相当，但Snappy算法在解压速度上要较LZO更快。Hadoop的会将大文件分割成HDFS block(默认64MB)大小的splits分片，每个分片对应一个Mapper程序。在这几个压缩算法中 BZip2、LZO、Snappy压缩是可分割的，Gzip则不支持分割。</code></pre></li></ol><h2 id="hive%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E4%BD%BF%E7%94%A8" tabindex="-1">Hive的安装与使用</h2><h4 id="%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC%E8%AF%B7%E9%98%85%E8%AF%BB%E4%BB%A5%E4%B8%8B%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99%EF%BC%8C%E5%90%8E%E6%9C%9F%E5%86%8D%E8%A1%8C%E5%AE%8C%E5%96%84" tabindex="-1">当前版本请阅读以下参考资料，后期再行完善</h4><ol><li><a href="https://blog.csdn.net/RonieWhite/article/details/104141634" target="_blank">hive的安装和使用</a></li><li><a href="https://blog.csdn.net/ddydavie/article/details/80667727" target="_blank">Hive入门及常用指令</a></li><li>更多进阶内容请自行百度拓展查阅</li></ol><h3 id="%E5%A6%82%E4%BD%95%E5%9C%A8hive%E4%B8%AD%E9%9B%86%E6%88%90hbase" tabindex="-1">如何在Hive中集成HBase</h3><ol><li>将Hbase的客户端<code>jar</code>拷贝至<code>Hive/lib</code>目录下</li><li>修改<code>hive/conf</code>下的<code>hive-site.xml</code>配置文件，添加如下属性：<pre><code class="language-">&lt;poperty&gt;&lt;name&gt;hbase.zookeeper.quorum&lt;/name&gt;&lt;value&gt;hadoop&lt;/value&gt;&lt;/poperty&gt;</code></pre></li><li>启动Hive，创建表管理表<code>hbase_table_1</code>，指定数据存储在Hbase表中，主要是通过<code>stored by HBaseStorageHandler</code>类来实现</li><li>往Hive表<code>hbase_table_1</code>表中插入数据</li></ol><h3 id="%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87-hivesql-%E6%9D%A5%E7%9B%B4%E6%8E%A5%E8%AF%BB%E5%86%99-hbase" tabindex="-1">如何通过 HiveSQL 来直接读写 HBase</h3><h4 id="%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC%E8%AF%B7%E9%98%85%E8%AF%BB%E4%BB%A5%E4%B8%8B%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99%EF%BC%8C%E5%90%8E%E6%9C%9F%E5%86%8D%E8%A1%8C%E5%AE%8C%E5%96%84-1" tabindex="-1">当前版本请阅读以下参考资料，后期再行完善</h4><ol><li><a href="https://zhuanlan.zhihu.com/p/74041611" target="_blank">如何整合hive和hbase</a></li><li><a href="https://www.cnblogs.com/cssdongl/p/6857891.html" target="_blank">HiveHbase集成实践</a></li><li>更多进阶内容请自行百度拓展查阅</li></ol><h2 id="hive%E7%9A%84%E5%88%86%E5%8C%BA%E5%92%8C%E5%88%86%E6%A1%B6" tabindex="-1">Hive的分区和分桶</h2><p>Hive的分区分桶都是数据存储和组织的策略，分区类似文件的分类归档，分桶类似于传统数据库的索引</p><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFhive%E5%88%86%E5%8C%BA" tabindex="-1">什么是Hive分区</h3><ol><li>Hive中数据库，表，及分区都是在HDFS存储的一个抽象</li><li>Hive中的一个分区对应的就是HDFS的一个目录，目录名就是分区字段</li><li>声明分区表 <code>PARTITIONED BY (name string)</code>，分区键不能和任何列重名</li><li>声明数据要导入的分区 <code>PARTITION(name=&quot;fx67ll&quot;)</code></li><li>查看分区 <code>SHOW PARTITIONAS</code></li><li>根据分区查询 <code>WHERE name = &quot;fx67ll&quot;</code></li><li>指定切分格式<pre><code class="language-">ROW FORMAT DELIMITED# 每个字段之间由[ , ]分割FIELDS TERMINATED BY &#39;,&#39;# 字段是Array形式，元素与元素之间由[ - ]分割COLLECTION ITEMS TERMINATED BY &#39;-&#39;# 字段是K-V形式，每组K-V对内部由[ : ]分割MAP KEYS TERMINATED BY &#39;:&#39;;</code></pre></li></ol><h3 id="hive%E5%88%86%E5%8C%BA%E7%9A%84%E4%BC%98%E7%82%B9" tabindex="-1">Hive分区的优点</h3><ol><li>如果一个表中有大量的数据，我们全部拿出来做查词的功能，耗时比较长，查询较慢，<br />使用了分区，就可以做到用到了那个分区就拿那个分区中的数据方便了查询，提高了查词的效率</li><li>横向分配数据，使得负载更为均衡</li></ol><h3 id="hive%E5%88%86%E5%8C%BA%E7%9A%84%E7%BC%BA%E7%82%B9" tabindex="-1">Hive分区的缺点</h3><ol><li>容易造成过多的小分区，过多的目录</li><li>如果分区策略不佳，容易导致分区数据不均衡，造成数据倾斜</li></ol><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFhive%E5%88%86%E6%A1%B6" tabindex="-1">什么是Hive分桶</h3><ol><li>分桶是相对分区进行更细粒度的划分，分桶将整个数据内容按照某列属性值得hash值进行区分，类似于<em>关系型数据的索引</em></li><li>如要安装id属性分为3个桶，就是对id属性值的hash值对3取摸，按照取模结果对数据分桶，<br />如取模结果为0的数据记录存放到一个文件，取模为1的数据存放到一个文件，取模为2的数据存放到一个文件</li><li>分桶之前要执行命令 <code>set hive.enforce.bucketing = true</code></li><li>声明分桶表 <code>CLUSTERED BY(id) INTO 3 BUCKETS</code></li></ol><h3 id="%E5%85%B3%E4%BA%8Ehive%E7%B4%A2%E5%BC%95%E7%9A%84%E8%AF%B4%E6%98%8E" tabindex="-1">关于Hive索引的说明</h3><ol><li>即从3.0开始索引已经被移除，有一些可替代的方案可能与索引类似：<ul><li>具有自动重写的物化视图可以产生非常相似的结果，Hive2.3.0增加了对物化视图视图的支持</li><li>使用列式文件格式（(Parquet、ORC）–他们可以进行选择性扫描；甚至可以跳过整个文件/块。很显然，例如我们创建表时使用的ORC格式就已经具有了索引的功能</li></ul></li><li>Hive为什么删除了索引：<ul><li>由于Hive是针对海量数据存储的，创建索引需要占用大量的空间，最主要的是Hive索引无法自动进行刷新，也就是当新的数据加入时候，无法为这些数据自动加入索引</li></ul></li></ol><h3 id="hive%E5%88%86%E6%A1%B6%E7%9A%84%E4%BC%98%E7%82%B9" tabindex="-1">Hive分桶的优点</h3><ol><li>分桶字段需要根据业务进行设定，可以解决数据倾斜问题，主要是在关联join的时候通过map端更快的连接</li><li>能够提供类似的哈希的快速响应，比分区更快</li></ol><h3 id="hive%E5%88%86%E6%A1%B6%E7%9A%84%E7%BC%BA%E7%82%B9" tabindex="-1">Hive分桶的缺点</h3><ol><li>需要在建表时规划好分桶策略，需要手动加载数据到分桶表</li><li>本质是空间换时间，时间换效率，所以在加载数据到表的时候有空间和时间上的消耗</li></ol><h3 id="hive%E4%B8%AD%E9%9D%99%E6%80%81%E5%88%86%E5%8C%BA%E5%92%8C%E5%8A%A8%E6%80%81%E5%88%86%E5%8C%BA%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">Hive中静态分区和动态分区的区别</h3><ol><li>静态分区与动态分区的主要区别在于静态分区是手动指定，而动态分区是通过数据来进行判断</li><li>详细来说，静态分区的列实在编译时期，通过用户传递来决定的；动态分区只有在SQL执行时才能决定</li><li>查询和写入的时候，静态分区键要用 <code>&lt;static partition key&gt; = &lt;value&gt;</code> 指定分区值；动态分区只需要给出分出分区键名称 <code>&lt;dynamic partition key&gt;</code></li><li>一张表可同时被静态和动态分区键分区，只是动态分区键需要放在静态分区建的后面，因为HDFS上的动态分区目录下不能包含静态分区的子目录</li></ol><h3 id="hive%E5%8A%A8%E6%80%81%E5%88%86%E5%8C%BA%E7%9A%84%E5%8F%82%E6%95%B0%E8%AE%BE%E5%AE%9A" tabindex="-1">Hive动态分区的参数设定</h3><ol><li>开启动态分区<pre><code class="language-"># 开启动态分区功能，默认false  set hive.exec.dynamic.partition = true  # 允许所有分区都是动态的，否则必须有静态分区字段，默认strict  set hive.exec.dynamic.partition.mode = nonstrict  </code></pre></li><li>动态分区参数调优<pre><code class="language-"># 每个mapper或reducer可以允许创建的最大动态分区个数，默认是100，超出则会报错  set hive.exec.max.dynamic.partitions.pernode = 1000# 一个动态分区语句可以创建的最大动态分区个数，默认是1000，超出报错set hive.exec.max.dynamic.partitions = 10000  # 全局可以创建的最大文件个数，默认是10000，超出报错  set hive.exec.max.created.files =100000  </code></pre></li></ol><h2 id="hive%E7%9A%84%E5%86%85%E9%83%A8%E8%A1%A8%E5%92%8C%E5%A4%96%E9%83%A8%E8%A1%A8" tabindex="-1">Hive的内部表和外部表</h2><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFhive%E7%9A%84%E5%86%85%E9%83%A8%E8%A1%A8%E5%92%8C%E5%A4%96%E9%83%A8%E8%A1%A8" tabindex="-1">什么是Hive的内部表和外部表</h3><ol><li>没有<code>external</code>修饰，表数据保存在Hive默认的路径下，数据完全由Hive管理，删除表时元数据(metadata)和表数据都会一起删除</li><li>有<code>external</code>修饰，表数据保存在HDFS上，该位置由用户指定，删除表时，只会删除表的元数据(metadata)</li></ol><h3 id="hive%E5%86%85%E9%83%A8%E8%A1%A8%E5%92%8C%E5%A4%96%E9%83%A8%E8%A1%A8%E7%9A%84%E5%8C%BA%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88" tabindex="-1">Hive内部表和外部表的区别是什么</h3><ol><li>内部表数据由Hive自身管理，外部表数据由HDFS管理</li><li>内部表数据存储的位置是<code>hive.metastore.warehouse.dir</code>，默认是 <code>/user/hive/warehouse</code></li><li>外部表数据的存储位置由自己制定，如果没有LOCATION，Hive将在HDFS上的<code>/user/hive/warehouse</code>文件夹下以外部表的表名创建一个文件夹，并将属于这个表的数据存放在这里</li><li>删除内部表会直接删除元数据(metadata)及存储数据</li><li>删除外部表仅仅会删除元数据(metadata)，HDFS上的文件并不会被删除</li><li>对内部表的修改会将修改直接同步给元数据(metadata)，而对外部表的表结构和分区进行修改，则需要修复 <code>MSCK REPAIR TABLE table_name</code></li></ol><h3 id="%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BB%BA%E8%AE%AE%E4%BD%BF%E7%94%A8%E5%A4%96%E9%83%A8%E8%A1%A8" tabindex="-1">生产环境中为什么建议使用外部表</h3><ol><li>因为外部表不会加载数据到Hive，减少数据传输，数据还能共享</li><li>Hive不会修改数据，所以无需担心数据的损坏</li><li>删除表时，只删除表结构，不删除数据</li></ol><h2 id="hive-sql" tabindex="-1">Hive SQL</h2><h3 id="hive%E4%B8%AD%E7%9A%84sql%E5%A6%82%E4%BD%95%E8%BD%AC%E5%8C%96%E6%88%90mapreduce%E4%BB%BB%E5%8A%A1%E7%9A%84" tabindex="-1">Hive中的SQL如何转化成MapReduce任务的</h3><ol><li><code>Antlr</code>定义SQL的语法规则，完成SQL词法，语法解析，将SQL转化为抽象语法树</li><li>遍历抽象语法树抽象出查询的基本组成单元 <code>QueryBlock</code></li><li>遍历<code>QueryBlock</code> ，翻译为执行操作树<code>OperatorTree</code></li><li>逻辑层优化器进行<code>OperatorTree</code>变换，合并不必要的<code>ReduceSinkOperator</code>，减少<code>shuffle</code>数据量</li><li>遍厉<code>OperatorTree</code>，翻译为<code>MapReduce</code>任务</li><li>物理层优化器进行<code>MapReduce</code>任务的变换，生成最终的执行计划</li></ol><h3 id="%E4%BB%80%E4%B9%88%E6%83%85%E5%86%B5%E4%B8%8Bhive%E4%B8%8D%E8%B5%B0mapreduce%E4%BB%BB%E5%8A%A1" tabindex="-1">什么情况下Hive不走MapReduce任务</h3><h3 id="hive%E4%B8%AD%E5%A6%82%E4%BD%95%E6%9F%A5%E8%AF%A2a%E8%A1%A8%E4%B8%ADb%E8%A1%A8%E4%B8%8D%E5%AD%98%E5%9C%A8%E7%9A%84%E6%95%B0%E6%8D%AE" tabindex="-1">Hive中如何查询A表中B表不存在的数据</h3><p><strong>题目：A、B两表，找出ID字段中，存在A表，但是不存在B表的数据。A表总共13w数据，去重后大约3W条数据，B表有2W条数据，且B表的ID字段有索引</strong></p><pre><code class="language-">select * from  Bwhere (select count(1) as num from A where A.ID = B.ID) = 0</code></pre><h3 id="hive%E4%B8%AD%E6%9C%89%E5%93%AA%E4%BA%9B%E8%BF%9E%E6%8E%A5%E6%9F%A5%E8%AF%A2%E4%BB%A5%E5%8F%8A%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8" tabindex="-1">Hive中有哪些连接查询以及如何使用</h3><h4 id="%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC%E8%AF%B7%E9%98%85%E8%AF%BB%E4%BB%A5%E4%B8%8B%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99%EF%BC%8C%E5%90%8E%E6%9C%9F%E5%86%8D%E8%A1%8C%E5%AE%8C%E5%96%84-2" tabindex="-1">当前版本请阅读以下参考资料，后期再行完善</h4><ol><li><a href="https://www.cnblogs.com/jnba/p/10673747.html" target="_blank">Hive——join的使用</a></li><li>更多进阶内容请自行百度拓展查阅</li></ol><h3 id="hive%E4%B8%AD%E5%B7%A6%E8%BF%9E%E6%8E%A5%E5%92%8C%E5%86%85%E8%BF%9E%E6%8E%A5%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">Hive中左连接和内连接的区别</h3><ol><li>内连接：连接的键匹配上就连接，没有匹配上就过滤掉</li><li>左连接：以左表为基准，与右表做关联，关联上则连接，右表关联不上的则为null</li></ol><h3 id="hive%E4%B8%AD%E5%B7%A6%E8%BF%9E%E6%8E%A5%E7%9A%84%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86" tabindex="-1">Hive中左连接的底层原理</h3><p><strong>参考下面Hive查询的时候on和where有什么区别的理解二</strong></p><h3 id="hive%E6%9F%A5%E8%AF%A2%E7%9A%84%E6%97%B6%E5%80%99-on-%E5%92%8C-where-%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB" tabindex="-1">Hive查询的时候 ON 和 WHERE 有什么区别</h3><h4 id="%E5%85%B1%E5%90%8C%E7%82%B9" tabindex="-1">共同点</h4><ol><li>on先执行，where后执行</li><li>并且where是对连接之后的结果进行的查询条件</li></ol><h4 id="%E7%AC%AC%E4%B8%80%E7%A7%8D%E7%90%86%E8%A7%A3%E6%96%B9%E5%BC%8F" tabindex="-1">第一种理解方式</h4><ol><li>条件不为主表条件的时候，放在on和where的后面一样</li><li>条件为主表条件的时候，放在on后面，结果为主表全量，放在where后面的时候为主表条件筛选过后的全量</li></ol><pre><code class="language-">1. select * from a left join b on a.id = b.id and a.dt=20181115;2. select * from a left join b on a.id = b.id and b.dt=20181115;3. select * from a join b on a.id = b.id and a.dt=20181115;4. select * from a left join b on a.id = b.id  where a.dt=20181115;sql1: 如果是left join 在on上写主表a的条件不会生效，全表扫描。sql2: 如果是left join 在on上写副表b的条件会生效，但是语义与写到where 条件不同sql3: 如果是inner join 在on上写主表a、副表b的条件都会生效sql4: 建议这么写，大家写sql大部分的语义都是先过滤数据然后再join，所以在不了解join on+条件的情况下，条件尽量别写在on后，直接写到where后就ok了，如果where条件后写b表的过滤条件，就变成了先left join出结果再按照b条件过滤数据  </code></pre><h4 id="%E7%AC%AC%E4%BA%8C%E7%A7%8D%E7%90%86%E8%A7%A3%E6%96%B9%E5%BC%8F" tabindex="-1">第二种理解方式</h4><ol><li><code>on</code>是在生成连接表的起作用的，<code>where</code>是生成连接表之后对连接表再进行过滤</li><li>当使用<code>left join</code>时，无论<code>on</code>的条件是否满足，都会返回左表的所有记录，对于满足的条件的记录，两个表对应的记录会连接起来，对于不满足条件的记录，那右表字段全部是null</li><li>当使用<code>right join</code>时，类似，只不过是全部返回右表的所有记录</li><li>当使用<code>inner join</code>时，功能与<code>where</code>完全相同</li></ol><pre><code class="language-">经过亲测后，更加深了对on和where的理解，得出以下结论：1.ON后的条件如果有过滤主表的条件，则结果对于不符合该条件的主表数据也会原条数保留，只是不匹配右表数据而已。对于on后面对右表的过滤条件，连接时会用该条件直接过滤右表数据后再和左边进行左连接。总之，对于不满足on后面的所有条件的数据，左表会在结果数据中原条数保留数据，只是不匹配右表数据而已。不满足条件的右表数据各字段会直接以NULL连接主表。2.ON后对左表的筛选条件对于结果行数会被忽略，但会影响结果中的匹配右表数据，因为只有符合左表条件的数据才会去和符合条件的右表数据进行匹配，不符合条件的左表数据会保留在最后结果中，但匹配的右表数据都是NULL.因此，对于需要过滤左表数据的话，需要把过滤条件放到where后面。3.ON后的左表条件（单独对左表进行的筛选条件）对于结果行数无影响，还是会返回所有左表的数据，但和右表匹配数据时，系统只会拿左表符合条件（ON后的对左表过滤条件）的数据去和右表符合条件（ON后的对右表过滤条件）的数据进行匹配抓取数据，而不符合条件的左表数据还是会出现在结果列表中，只是对应的右表数据都是NULL。4.ON后的右表条件（单独对右表进行的筛选条件）会先对右表进行数据筛选后再和左表做连接查询，对结果行数有影响（当左表对右表是一对多时），但不会影响左表的显示行数，然后拿符合条件的右表数据去和符合条件的左表数据进行匹配。5.Where还是对连接后的数据进行过滤筛选，这个无异议。6.匹配数据时无论左右表，都是拿符合ON后的过滤条件去做数据匹配，不符合的会保留左表数据，用NULL填充右表数据。综上得出，ON后面对于左表的过滤条件，在最后结果行数中会被忽略，并不会先去过滤左表数据再连接查询，但是ON后的右表条件会先过滤右表数据再连接左表进行查询。连接查询时，都是用符合ON后的左右表的过滤条件的数据进行连接查询，只有符合左右表过滤条件的数据才能正确匹配，剩下的左表数据会正常出现在结果集中，但匹配的右表数据是NULL。因此对于左表的过滤条件切记要放到Where后，对于右表的过滤条件要看情况了。如果需要先过滤右表数据就把条件放到ON后面即可。</code></pre><h2 id="hive-%E5%87%BD%E6%95%B0" tabindex="-1">Hive 函数</h2><h3 id="%E5%85%B3%E4%BA%8E-udf%2Fudaf%2Fudtf-%E7%9A%84%E6%8F%90%E9%97%AE" tabindex="-1">关于 UDF/UDAF/UDTF 的提问</h3><ol><li>如何使用UDF/UDAF/UDTF</li><li>为什么使用UDF/UDAF/UDTF</li><li>你写过什么样的UDF/UDAF/UDTF</li><li>Hive自定义函数实现了什么函数<br /><em>上述四个问题自行 <a href="https://blog.csdn.net/sinat_30316741/article/details/113753651" target="_blank">参考资料</a> 并结合工作中实际场景来作答，没有标准答案</em></li></ol><h3 id="hive%E4%B8%AD%E5%A6%82%E4%BD%95%E5%8E%BB%E9%87%8D" tabindex="-1">Hive中如何去重</h3><h4 id="%E7%AC%AC%E4%B8%80%E7%A7%8D%E6%96%B9%E5%BC%8F%EF%BC%9A%E4%BD%BF%E7%94%A8-distinct" tabindex="-1">第一种方式：使用 <code>DISTINCT</code></h4><ol><li>对select 后面所有字段去重，并不能只对一列去重</li><li>当<code>DISTINCT</code>应用到多个字段的时候，<code>DISTINCT</code>必须放在开头，其应用的范围是其后面的所有字段，而不只是紧挨着它的一个字段，而且<code>DISTINCT</code>只能放到所有字段的前面</li><li><code>DISTINCT</code>对<code>NULL</code>是不进行过滤的，即返回的结果中是包含<code>NULL</code>值的</li><li>聚合函数中的<code>DISTINCT</code>,如<code>count()</code>会过滤掉为<code>NULL</code></li></ol><h4 id="%E7%AC%AC%E4%BA%8C%E7%A7%8D%E6%96%B9%E5%BC%8F%EF%BC%9A%E4%BD%BF%E7%94%A8-group-by" tabindex="-1">第二种方式：使用 <code>GROUP BY</code></h4><ol><li>对<code>GROUP BY</code>后面所有字段去重，并不能只对一列去重</li></ol><h4 id="%E7%AC%AC%E4%B8%89%E7%A7%8D%E6%96%B9%E5%BC%8F%EF%BC%9A%E4%BD%BF%E7%94%A8-row_number()-over-%E7%AA%97%E5%8F%A3%E5%87%BD%E6%95%B0" tabindex="-1">第三种方式：使用 <code>ROW_NUMBER() OVER</code> 窗口函数</h4><ol><li>参考资料一：<a href="https://blog.csdn.net/shuaiqig/article/details/116228534" target="_blank">一种巧妙的hive sql数据去重方法</a></li><li>参考资料二：<a href="https://blog.csdn.net/ABCDEFG0929/article/details/89190450" target="_blank">Hive–数据去重及row_number()</a></li><li>参考资料三：<a href="https://blog.csdn.net/yimingsilence/article/details/70140877" target="_blank">Hive(十一)–数据去重及row_number()</a></li></ol><h3 id="hive%E4%B8%AD%E6%8E%92%E5%BA%8F%E5%87%BD%E6%95%B0%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F%E5%8F%8A%E5%8C%BA%E5%88%AB" tabindex="-1">Hive中排序函数的使用方式及区别</h3><ol><li><code>order by</code> 会对输入做全局排序，为保证全局的排序，因此只有一个reducer，会导致当输入规模较大时，需要较长的计算时间。</li><li><code>sort by</code>不是全局排序，其在数据进入reducer前完成排序。因此，如果用<code>sort by</code>进行排序，则<code>sort by</code>只保证每个reducer的输出有序，不保证全局有序。</li><li><code>distribute by 字段</code> 根据指定的字段将数据分到不同的reducer，且分发算法是hash散列，常用<code>sort by</code>结合使用，Hive要求<code>distribute by</code>语句要写在<code>sort by</code>语句之前。</li><li><code>cluster by 字段</code> 除了具有<code>distribute by</code>的功能(既可以把数据分到不同的reduce)外，还会对该字段进行排序。但是排序只能是倒序排序，不能指定排序规则为<code>asc</code>或者<code>desc</code></li><li>因此：<ul><li>当数据量规模较大时，不使用 <code>order by</code>，使用用 <code>distribute by + sort by</code></li><li>如果 <code>distribute by</code> 和 <code>sort by</code> 字段是同一个时，此时，<code>cluster by = distribute by + sort by</code></li></ul></li></ol><h3 id="hive%E4%B8%AD%E9%83%A8%E5%88%86%E9%AB%98%E9%A2%91%E5%87%BD%E6%95%B0-%E2%80%94%E2%80%94%E2%80%94%E2%80%94-split-%2F-coalesce-%2F-collect-list-%2F-collect-set" tabindex="-1">Hive中部分高频函数 ———— <code>split</code> / <code>coalesce</code> / <code>collect list</code> / <code>collect set</code></h3><ol><li><a href="https://blog.csdn.net/qq_34105362/article/details/80408621" target="_blank">Hive ———— split</a></li><li><a href="https://blog.csdn.net/weixin_42784951/article/details/114653922" target="_blank">Hive ———— coalesce</a></li><li><a href="https://blog.csdn.net/LINBE_blazers/article/details/89198019" target="_blank">Hive ———— collect list/collect set</a></li></ol><h3 id="hive%E5%B8%B8%E7%94%A8%E5%87%BD%E6%95%B0" tabindex="-1">Hive常用函数</h3><ol><li><a href="https://blog.csdn.net/weixin_44318830/article/details/110738201" target="_blank">Hive常用的函数总结</a></li><li><a href="https://blog.csdn.net/yezonghui/article/details/121293294" target="_blank">Hive函数大全</a></li></ol><h2 id="hive-%E8%BF%90%E7%BB%B4" tabindex="-1">Hive 运维</h2><h3 id="%E5%A6%82%E4%BD%95%E7%9B%91%E6%8E%A7%E4%B8%80%E4%B8%AA%E6%8F%90%E4%BA%A4%E5%90%8E%E7%9A%84hive%E7%8A%B6%E6%80%81" tabindex="-1">如何监控一个提交后的Hive状态</h3><ol><li>使用java代码提交Hive，通过HiveStatement获取日志数据并解析出<code>application_id</code></li><li>就可以通过<code>application_id</code>去yarn上查看运行状态</li></ol><h2 id="hive-%E4%BC%98%E5%8C%96" tabindex="-1">Hive 优化</h2><p><em>该模块请参考我关于<code>Hive优化</code>的文章</em></p><ol><li>点击访问 ————&gt; <a href="https://fx67ll.xyz/archives/hive-optimize-inwork" target="_blank">Hive在工作中的调优总结</a></li><li>点击访问 ————&gt; <a href="https://fx67ll.xyz/archives/hive-sql-inwork" target="_blank">HiveSQL工作实战总结</a></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llBigData/blob/main/interview/hive/hive.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[从零开始的常用MySQL语句练习大全]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/mysql-practice" />
                <id>tag:https://fx67ll.xyz,2021-12-14:mysql-practice</id>
                <published>2021-12-14T12:28:29+08:00</published>
                <updated>2025-07-02T11:29:52+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E5%85%88%E8%AF%B4%E4%B8%80%E4%BA%9B%E5%BA%9F%E8%AF%9D" tabindex="-1">先说一些废话</h3><p>很多时候深入学习固然很重要，但是想要写下一篇给新手都能看得懂看的很香，并且老鸟可以查漏补缺的的练习博客，还是挺有难度，<br />所以今天尝试写一些关于MySQL的语句练习大全，供想要从零开始练习MySQL的新手们去学习。</p><p>需要注意的是写代码是一种脑力和操作并行的技术，建议没怎么写过SQL的人一定要先从命令行的语句练习开始，这样才能够更好的加深印象！！！</p><p>所有的语句我会写一些基本的注释，需要深入学习的童鞋可以参考单个知识点继续了解。</p><p>当然今天列出的所有语句都有测试过，但是不确保没有手误打错的时候，如果有什么错误的地方请在评论区指出，非常感谢！！！</p><p><em><strong>提示信息：本文测试所使用的MySQL版本是<code>5.7.26</code></strong></em></p><h3 id="%E5%85%B6%E4%BD%99%E5%BA%9F%E8%AF%9D%E4%B8%8D%E5%A4%9A%E8%AF%B4%E7%9B%B4%E6%8E%A5%E4%B8%8A%E4%BB%A3%E7%A0%81" tabindex="-1">其余废话不多说直接上代码</h3><h4 id="%E8%BF%9E%E6%8E%A5mysql" tabindex="-1">连接MySQL</h4><pre><code class="language-sql"># 用户名和密码要紧跟-u-p不能有空格mysql -u用户名 -p密码# 如果不想明文显示密码，执行命令后会提示输入密码，并且不会明文显示密码mysql -u 用户名 -p</code></pre><h4 id="%E6%95%B0%E6%8D%AE%E5%BA%93" tabindex="-1">数据库</h4><pre><code class="language-sql"># 查看所有的数据库SHOW DATABASES;# 创建一个数据库CREATE DATABASE test;# 删除一个数据库DROP DATABASE test;# 使用数据库USE test;</code></pre><h4 id="%E8%A1%A8" tabindex="-1">表</h4><pre><code class="language-sql"># 查看所有的表SHOW TABLES;# =======================================================# 知识点提示！！！# 主键约束（PRIMARY KEY）：没有明确的概念定义，是唯一性索引的一种，不能重复，一个表只能有一个主键# 自动增加（AUTO_INCREMENT）：自动增加（需要和主键PRIMARY KEY一起使用）# 非空约束（NOT NULL）：要求被装饰的字段非空# 外键约束（FOREIGN KEY）：用来在表与表的数据之间建立链接，它可以是一列或者多列  # 举个栗子帮助理解：班级表，10个班级，年级表，6个年级，每个班级只能所属1个年级，# 且必须是这6个年级中的1个，这个时候班级表就需要用年级表来建立外键，确保数据一致且完整  # 唯一约束（UNIQUE KEY）：指所有记录中的字段的值不能重复出现，可以联合非空UNIQUE(字段1，字段2)  # 数据类型，务必深入了解，对数据库的优化非常重要  # AS 别名或者连接语句的操作符# =======================================================# 创建一个表，拥有主键CREATE TABLE test(id INT, name VARCHAR(10), PRIMARY KEY (id));# 创建另一个表，拥有主键，并包含前一个表的外键约束，以及唯一约束  CREATE TABLE test_key(id INT, name VARCHAR(10), PRIMARY KEY (id), FOREIGN KEY (id) REFERENCES test(id), UNIQUE (name));# 直接将查询结果导入或复制到，一个新创建的表CREATE TABLE test_copy SELECT * FROM test;CREATE TABLE test_copy_as AS SELECT * FROM test;# 将一个已存在表的数据结构克隆到，一个新创建的表CREATE TABLE test_clone LIKE test;# 创建一个临时表，各种方式  # 临时表将在你连接MySQL期间存在，当断开连接时，MySQL将自动删除表并释放所用的空间，也可手动删除。CREATE TEMPORARY TABLE test_tem(id INT, name VARCHAR(10));CREATE TEMPORARY TABLE test_tem_copy SELECT * FROM test;# 删除一个存在表DROP TABLE IF EXISTS test;# 更改存在表的名称ALTER TABLE test RENAME test_rename;RENAME TABLE test_rename TO test;# 查看表的结构# 以下五条语句效果相同，推荐第一条，因为简单  DESC test;DESCRIBE test;SHOW COLUMNS IN test;SHOW COLUMNS FROM test;EXPLAIN test;# 查看表的创建语句SHOW CREATE TABLE test;</code></pre><h4 id="%E8%A1%A8%E7%BB%93%E6%9E%84" tabindex="-1">表结构</h4><pre><code class="language-sql"># 添加表字段ALTER TABLE test ADD age VARCHAR(2);# 删除表字段ALTER TABLE test DROP age;# 更改表字段和表字段属性ALTER TABLE test CHANGE age age_change INT;# 只更改表字段属性ALTER TABLE test MODIFY age VARCHAR(7);# 查询所有表信息SHOW TABLE STATUS;</code></pre><h4 id="%E8%A1%A8%E6%95%B0%E6%8D%AE" tabindex="-1">表数据</h4><pre><code class="language-sql"># 增加表数据INSERT INTO test VALUES (1, &#39;alpha&#39;, &#39;24&#39;), (2, &#39;beta&#39;, &#39;25&#39;), (3, &#39;delta&#39;, &#39;27&#39;);# =======================================================# 因为test表设置了主键无法用下面这种方式增加，前两个语句先重建一张表CREATE TABLE test_insert(id INT, name VARCHAR(8));INSERT INTO test_insert VALUES (1, &#39;alpha&#39;), (2, &#39;beta&#39;); # =======================================================# 增加查询之后的表数据  INSERT INTO test_insert SELECT * FROM test_insert;# 删除表数据DELETE FROM test_insert WHERE id = 2;# 更改表数据UPDATE test_insert SET name = &#39;delta&#39; WHERE id = 1;# =======================================================# 为了下面的查询语句能够更直观一些我们再插入一些数据并把age_change的表字段改为ageINSERT INTO test VALUES (10, &#39;gamma&#39;, &#39;39&#39;), (6, &#39;zeta&#39;, &#39;30&#39;), (19, &#39;theta&#39;, &#39;10&#39;), (8, &#39;eta&#39;, &#39;20&#39;), (46, &#39;zeta&#39;, &#39;11&#39;),  (56, &#39;zeta&#39;, &#39;9&#39;), (66, &#39;zeta&#39;, &#39;3&#39;);ALTER TABLE test CHANGE age_change age VARCHAR(20);# =======================================================# =======================================================# 知识点提示！！！# SELECT DISTINCT * FROM &#39;表名&#39; WHERE &#39;限制条件&#39; GROUP BY &#39;分组依据&#39; HAVING &#39;过滤条件&#39; # ORDER BY LIMIT &#39;展示条数&#39; OFFSET &#39;跳过的条数&#39;# 以上关键字使用顺序不能错误，否则会产生错误信息# DISTINCT 去重，注意如果是多个表字段去重，只有每个表字段都相同才会认为相同# * 代表通配符，会返回所有字段数据# WHERE 语句用来包含查询条件，对原始记录过滤# HAVING 语句也是用来包含查询条件，但是HAVING是对WHERE查询分组之后的记录继续过滤# ORDER BY 排序默认正序也就是升序，DESC表示反序也就是降序# LIMIT 属性来设定返回的记录数，一般用于列表的分页# OFFSET 属性来设定跳过的返回的记录数，一般配合LIMIT# =======================================================# 查找表数据SELECT * FROM test;# 查询去除重复数据之后的表数据SELECT DISTINCT name FROM test;# 根据表字段查找表数据SELECT * FROM test WHERE name = &#39;zeta&#39;;# 查找name为zeta并且id大于30的表数据SELECT * FROM test WHERE name = &#39;zeta&#39; HAVING id &gt; 30;# 根据id分组查询age小于20的表数据SELECT * FROM test GROUP BY id HAVING age &lt; 20;# 查找根据name排序之后的表数据SELECT * FROM test ORDER BY name;# 查找根据name反序排序之后的表数据SELECT * FROM test ORDER BY name DESC; # =======================================================# 注意下面这个排序，如果排序的第一个字段所有值都不同，那么第二列排序就没有意义了，# 所以我们之前加入了一些name相同的值，所以可以我们可以看下zeta字段的age排序# =======================================================# 查找先根据name排序，再根据age排序之后的表数据SELECT * FROM test ORDER BY name, age;# 查找先根据age反序，再根据name排序之后，年龄最大的前三位的表数据SELECT * FROM test ORDER BY age DESC, name LIMIT 3;# 查找先根据age反序，再根据name排序之后，年龄最大的前三位，并跳过第一条的表数据SELECT * FROM test ORDER BY age DESC, name LIMIT 3 OFFSET 1;SELECT * FROM test ORDER BY age DESC, name LIMIT 1,3;# 使用正则查询name表字段中包含字母g的表数据SELECT * FROM test WHERE name regexp &#39;.*[g]+.*&#39;;</code></pre><h4 id="%E8%A1%A8%E6%95%B0%E6%8D%AE-%E2%80%94%E2%80%94-%E8%BF%9E%E6%8E%A5%E6%9F%A5%E8%AF%A2" tabindex="-1">表数据 —— 连接查询</h4><pre><code class="language-sql"># =======================================================# 为了方便大家更为直观的看到连接查询的效果，我再重新建一个用于连接的表CREATE TABLE test_join(id INT, join_name VARCHAR(20), join_desc VARCHAR(100));INSERT INTO test_join VALUES (10, &#39;join_a&#39;, &#39;desc_a&#39;), (11, &#39;join_b&#39;, &#39;desc_b&#39;), (10, &#39;gamma&#39;, &#39;join_gamma&#39;);# =======================================================# =======================================================# 解释一下下面的内连接查询为什么会有三种# 第一种为不设置别名的写法# 第二种为简写AS设置别名的写法# 第三种为有AS关键字设置别名的写法# 后续均使用第二种写法# =======================================================# 内连接查询# 注意JOIN连接默认使用内连接SELECT * FROM test INNER JOIN test_join ON test.id = test_join.id;SELECT * FROM test m INNER JOIN test_join ON m.id = n.id;SELECT * FROM test AS m INNER JOIN test_inert AS n ON m.id = n.id;# 左外连接查询SELECT * FROM test m LEFT JOIN test_join n ON m.id = n.id;# 左外连接查询，左表独有数据SELECT * FROM test m LEDT JOIN test_join n ON m.id = n.id WHERE n.id IS NULL;# 右外连接查询SELECT * FROM test m RIHGT JOIN test_join n ON m.id = n.id;# 右外连接查询，右表独有数据SELECT * FROM test m RIGHT JOIN test_join n ON m.id = n.id WHERE m.id IS NULL;# 交叉连接查询，又叫笛卡尔连接SELECT * FROM test CROSS JOIN test_join;SELECT * FROM test,test_join;# 全连接查询  # UNION默认不返回重复的数据，UNION ALL则会返回重复的数据SELECT id,name FROM test UNION SELECT id,join_name FROM test_join;SELECT id,name FROM test UNION ALL SELECT id,join_name FROM test_join;</code></pre><h4 id="%E9%94%AE" tabindex="-1">键</h4><pre><code class="language-sql"># =======================================================# 这里是指定主键约束名的添加和删除方式，但是主键只能有一个，所以感觉主键指定名称貌似有点多余？ALTER TABLE test ADD CONSTRAINT pk_test PRIMARY KEY (id);ALTER TABLE test DROP PRIMARY KEY &#96;pk_test&#96;;# =======================================================# 添加主键ALTER TABLE test ADD PAIMARY KEY (id);# 删除主键ALTER TABLE test DROP PRIMARY KEY;# =======================================================# 知识点提示！！！# 不指定外键约束名，会自动生成一个，但是不知道怎么查出来，# 有人说用SHOW CREATE TABLE 表名 可以查询出来但是我测试了一下不行# 这个问题先放着，后期我来填坑# =======================================================# =======================================================# 外键增删改查，都要通过外键的约束名，创建时尽量注意写外键的约束名，# 所以不建议下面这种不指定外键约束名的方式ALTER TABLE test ADD FOREIGN KEY (id) REFERENCES test_join(id);# =======================================================# =======================================================# 建两个表用于测试CREATE TABLE test_key(id INT, key_name VARCHAR(20), PRIMARY KEY(id));CREATE TABLE test_foreign_key(id INT, foreign_key_name VARCHAR(20), PRIMARY KEY(id));# =======================================================# 添加外键ALTER TABLE test_key ADD CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES test_foreign_key(id);# 删除外键ALTER TABLE test_key DROP FOREIGN KEY &#96;fk_id&#96;;# 修改外键ALTER TABLE test_key DROP FOREIGN KEY &#96;fk_id&#96;, ADD CONSTRAINT fk_id_new FOREIGN KEY (id) REFERENCES test_foreign_key(id);# 添加唯一键# 这种不指定唯一键约束名的方式，可以用SHOW CREATE TABLE 查看约束名ALTER TABLE test_key ADD UNIQUE(key_name);# 添加唯一键，指定键名ALTER TABLE test_key ADD UNIQUE un_name (name);ALTER TABLE test_key ADD UNIQUE INDEX un_name (name);ALTER TABLE test_key ADD CONSTRAINT un_name UNIQUE (name);CREATE UNIQUE INDEX un_name ON test_key(name);# 删除唯一键DROP INDEX un_name ON test_key;# 添加索引ALTER TABLE test_key ADD INDEX (key_name);# 添加索引，指定索引名ALTER TABLE test_key ADD INDEX in_name (key_name);CREATE INDEX in_name ON test_key(key_name);# 删除索引DROP INDEX in_name ON test_key;</code></pre><h4 id="%E5%87%BD%E6%95%B0" tabindex="-1">函数</h4><pre><code class="language-sql"># =======================================================# 聚合函数# =======================================================# 求总数SELECT count(id) AS total FROM test;# 求和SELECT sum(age) AS total_age FROM test;# 求平均值SELECT avg(age) AS avg_age FROM test;# 求最大值SELECT max(age) AS max_age FROM test;# 求最小值SELECT min(age) AS min_age FROM test;# =======================================================# 数学函数# =======================================================# 绝对值：8SELECT abs(-8);# 二进制：1010，八进制：12，十六进制：ASELECT bin(10), oct(10), hex(10);# 圆周率：3.141593SELECT pi();# 大于x的最小整数值：6SELECT ceil(5.5);# 小于x的最大整数值：5SELECT floor(5.5);# 返回集合中最大的值：86SELECT greatest(13, 21, 34, 41, 55, 69, 72, 86);# 返回集合中最小的值：13SELECT least(13, 21, 34, 41, 55, 69, 72, 86);# 余数：3SELECT mod(1023, 10);# 返回0-1内的随机值，每次不一样SELECT rand();# 提供一个参数生成一个指定值SELECT rand(9);  # 0.406868412538309SELECT rand(&#39;test123&#39;);  # 0.15522042769493574# 四舍五入：1023SELECT round(1023.1023);# 四舍五入，保留三位数：1023.102SELECT round(1023.1023, 3);# 四舍五入整数位：SELECT round(1023.1023, -1);  # 1020SELECT round(1025.1025, -1);  # 1030，注意和truncate的区别# 截短为3位小数：1023.102SELECT truncate(1023.1023, 3);# 截短为-1位小数：1020SELECT truncate(1023.1023, -1);  # 1020SELECT truncate(1025.1025, -1);  # 1020，注意和round的区别# 符号的值的符号(负数，零或正)对应-1，0或1SELECT sign(-6);  # -1SELECT sign(0);  # 0SELECT sign(6);  # 1# 平方根：10SELECT sqrt(10);# =======================================================# 字符串函数# =======================================================# 连接字符串 &#39;fx67ll&#39;SELECT concat(&#39;f&#39;, &#39;x&#39;, &#39;67&#39;, &#39;ll&#39;);# 用分隔符连接字符串 &#39;fx67ll&#39;，注意如果分隔符为NULL，则结果为NULLSELECT concat_ws(&#39;-&#39;, &#39;fx&#39;, &#39;6&#39;, &#39;7&#39;, &#39;l&#39;, &#39;l&#39;);  # fx-6-7-l-lSELECT concat_ws(NULL, &#39;fx&#39;, &#39;6&#39;, &#39;7&#39;, &#39;l&#39;, &#39;l&#39;);  # NULL# 将字符串 &#39;fx67ll&#39; 从3位置开始的2个字符替换为 &#39;78&#39;SELECT insert(&#39;fx67ll&#39;, 3, 2, &#39;78&#39;);  # fx78ll# 返回字符串 &#39;fx67ll&#39; 左边的3个字符：fx6SELECT left(&#39;fx67ll&#39;, 3);# 返回字符串 &#39;fx67ll&#39; 右边的4个字符: 67llSELECT right(&#39;fx67ll&#39;, 4);# 返回字符串 &#39;fx67ll&#39; 第3个字符之后的子字符串：67llSELECT substring(&#39;fx67ll&#39;, 3);# 返回字符串 &#39;fx67ll&#39; 倒数第3个字符之后的子字符串：7llSELECT substring(&#39;fx67ll&#39;, -3);# 返回字符串 &#39;fx67ll&#39; 第3个字符之后的2个字符：67SELECT substring(&#39;fx67ll&#39;, 3, 2);# 切割字符串 &#39; fx67ll &#39; 两边的空字符，注意字符串左右有空格：&#39;fx67ll&#39;SELECT trim(&#39; fx67ll &#39;);# 切割字符串 &#39; fx67ll &#39; 左边的空字符：&#39;fx67ll &#39;SELECT ltrim(&#39; fx67ll &#39;);# 切割字符串 &#39; fx67ll &#39; 右边的字符串：&#39; fx67ll&#39;SELECT rtrim(&#39; fx67ll &#39;);# 重复字符 &#39;fx67ll&#39; 三次：fx67llfx67llfx67llSELECT repeat(&#39;fx67ll&#39;, 3);# 对字符串 &#39;fx67ll&#39; 进行反向排序：ll76xfSELECT reverse(&#39;fx67ll&#39;);# 返回字符串的长度：6SELECT length(&#39;fx67ll&#39;);# 对字符串进行大小写处理，大小写各两种方式SELECT upper(&#39;FX67ll&#39;);  # FX67LLSELECT lower(&#39;fx67LL&#39;);  # fx67llSELECT ucase(&#39;fX67Ll&#39;);  # FX67LLSELECT lcase(&#39;Fx67lL&#39;);  # fx67ll# 返回 &#39;f&#39; 在 &#39;fx67ll&#39; 中的第一个位置：1SELECT position(&#39;f&#39; IN &#39;fx67ll&#39;);# 返回 &#39;1&#39; 在 &#39;fx67ll&#39; 中的第一个位置，不存在返回0：0SELECT position(&#39;1&#39; IN &#39;fx67ll&#39;);# 比较字符串，第一个参数小于第二个返回负数，否则返回正数，相等返回0SELECT strcmp(&#39;abc&#39;, &#39;abd&#39;);  # -1SELECT strcmp(&#39;abc&#39;, &#39;abb&#39;);  # 1SELECT strcmp(&#39;abc&#39;, &#39;abc&#39;);  # 0# =======================================================# 时间函数# =======================================================# 返回当前日期，时间，日期时间SELECT current_date, current_time, now();# 返回当前时间的时，分，秒SELECT hour(current_time), minute(current_time), second(current_time);# 返回当前日期的年，月，日SELECT year(current_date), month(current_date), day(current_date);# 返回当前日期的季度SELECT quarter(current_date);# 返回当前月份的名称，当前星期的名称SELECT monthname(current_date), dayname(current_date);# 返回当前日在星期的天数，当前日在月的天数，当前日在年的天数SELECT dayofweek(current_date), dayofmonth(current_date), dayofyear(current_date);# =======================================================# 控制流函数# =======================================================# IF判断：1SELECT IF(2&gt;1, &#39;1&#39;, &#39;0&#39;)  # 1# IFNULL判断# 判断第一个表达式是否为NULL，如果为NULL则返回第二个参数的值，否则返回第一个参数的值SELECT IFNULL(NULL, 1);  # 1SELECT IFNULL(&#39;fx67ll&#39;, 0);  # fx67ll# ISNULL判断# 接受1个参数，并测试该参数是否为NULL，如果参数为NULL，则返回1，否则返回0SELECT ISNULL(1);  # 0SELECT ISNULL(1/0);  # 1# NULLIF判断# 接受2个参数，如果第1个参数等于第2个参数，则返回NULL，否则返回第1个参数SELECT NULLIF(&#39;fx67ll&#39;, &#39;fx67ll&#39;);  # NULLSELECT NULLIF(&#39;fx67ll&#39;, &#39;ll76xf&#39;);  # fx67ll# NULLIF类似于下面的CASE表达式CASE WHEN expression_1 = expression_2   THEN NULLELSE   expression_1END;# CASE判断：secondSELECT CASE 2WHEN 1 THEN &#39;first&#39;WHEN 2 THEN &#39;second&#39;WHEN 3 THEN &#39;third&#39;ELSE &#39;other&#39;END;# =======================================================# 系统信息函数# =======================================================# 显示当前数据库名SELECT database();# 显示当前用户idSELECT connection_id();# 显示当前用户SELECT user();# 显示当前mysql版本SELECT version();# 返回上次查询的检索行数SELECT found_rows();</code></pre><h4 id="%E8%A7%86%E5%9B%BE" tabindex="-1">视图</h4><pre><code class="language-sql"># 创建视图CREATE VIEW v AS SELECT id,name FROM test;CREATE VIEW sv(sid,sname) AS SELECT id,name FROM test;# =======================================================# 知识点提示！！！# MySQL视图规定FROM关键字后面不能包含子查询# 修改了视图对基表数据会有影响，反之，修改了基表数据对视图也会有影响# =======================================================# 查看创建视图语句SHOW CREATE VIEW v;# 查看视图结构DESC v;DESCRIBE v;SHOW COLUMNS IN v;SHOW COLUMNS FROM v;EXPLAIN v;# 查看视图数据SELECT * FROM v;# 修改视图CREATE OR REPLACE VIEW v AS SELECT name,age FROM test;ALTER VIEW v AS SELECT age FROM test;# 删除视图DROP v IF EXISTS v;# =======================================================# 知识点提示！！！# 使用视图的好处：# 1. 安全，只查询需要的数据  # 2. 性能，避免过多使用JOIN查询  # 3. 灵活，放弃使用一些过旧的表  # =======================================================</code></pre><h4 id="%E5%AD%98%E5%82%A8%E8%BF%87%E7%A8%8B" tabindex="-1">存储过程</h4><pre><code class="language-sql"># 因为存储过程比较灵活，这里不写具体示例，主要写一些定义语法  # 声明语句结束符DELIMITER $$# =======================================================# 知识点提示！！！# 上面的($$)结束符可以自己定义，MySQL中默认是;   # =======================================================# 声明存储过程CREATE PROCEDURE 存储过程名( [IN | OUT | INOUT] 参数 数据类型)# =======================================================# 知识点提示！！！# IN 输入参数  OUT 输出参数  INOUT 输入输出参数     # =======================================================# 声明存储函数CREATE FUNCTION 存储函数名(参数 数据类型)# 声明存储过程开始和结束BEGIN ...... END# 声明变量DECLARE 变量名 变量类型 默认值# 变量赋值SET @变量名 = 值# 调用存储过程CALL 存储过程名(传参)# 注释-- 注释体# 查看存储过程的详细SHOW CREATE PROCEDURE 数据库.存储过程名  # 修改存储过程ALTER PROCEDURE# 删除存储过程DROP PROCEDURE# =======================================================# 知识点提示！！！# 把过多的业务逻辑写在存储过程汇中不利于维护管理，# 除了个别对业务性能要求较高的业务，其他的必要性不是很大    # =======================================================</code></pre><h4 id="%E5%A4%87%E4%BB%BD%E8%BF%98%E5%8E%9F" tabindex="-1">备份还原</h4><pre><code class="language-sql"># 备份命令# 选项参考下方列表内容mysqldump [选项] 数据库名 [表名] &gt; 脚本名mysqldump [选项] --数据库名 [选项 表名] &gt; 脚本名# 备份所有数据库命令mysqldump [选项] --all-databases [选项]  &gt; 脚本名# =======================================================# 知识点提示！！！# 下方在导入备份数据库前，db_name如果没有，是需要创建的，# 而且与db_name.db中数据库名是一样的才可以导入# =======================================================# 系统行还原命令mysqladmin -uroot -p create db_name mysql -uroot -p  db_name &lt; /backup/mysqldump/db_name.db# source还原命令mysql &gt; use db_namemysql &gt; source /backup/mysqldump/db_name.db# =======================================================# 知识点提示！！！# 不同的业务场景与规模有不同的备份策略  # 双机热备：通过日志文件来传输服务器上的数据变化，主服务器上数据被更新时触发，# 传输相应的日志文件到从服务器，并通过日志文件，作出相应的操作  # =======================================================</code></pre><h5 id="%E5%A4%87%E4%BB%BD%E5%91%BD%E4%BB%A4%E9%80%89%E9%A1%B9%E8%AF%B4%E6%98%8E" tabindex="-1">备份命令选项说明</h5><table><thead><tr><th style="text-align:center">参数名</th><th style="text-align:center">缩写</th><th style="text-align:center">含义</th></tr></thead><tbody><tr><td style="text-align:center">–host</td><td style="text-align:center">-h</td><td style="text-align:center">服务器IP地址</td></tr><tr><td style="text-align:center">–port</td><td style="text-align:center">-P</td><td style="text-align:center">服务器端口号</td></tr><tr><td style="text-align:center">–user</td><td style="text-align:center">-u</td><td style="text-align:center">MySQL用户名</td></tr><tr><td style="text-align:center">–password</td><td style="text-align:center">-p</td><td style="text-align:center">MySQL密码</td></tr><tr><td style="text-align:center">–databases</td><td style="text-align:center"></td><td style="text-align:center">指定要备份的数据库</td></tr><tr><td style="text-align:center">–all-databases</td><td style="text-align:center"></td><td style="text-align:center">备份MySQL服务器上的所有数据库</td></tr><tr><td style="text-align:center">–compact</td><td style="text-align:center"></td><td style="text-align:center">压缩模式，产生更少的输出</td></tr><tr><td style="text-align:center">–comments</td><td style="text-align:center"></td><td style="text-align:center">添加注释信息</td></tr><tr><td style="text-align:center">–complete-insert</td><td style="text-align:center"></td><td style="text-align:center">输出完成的插入语句</td></tr><tr><td style="text-align:center">–lock-tables</td><td style="text-align:center"></td><td style="text-align:center">备份前，锁定所有数据库表</td></tr><tr><td style="text-align:center">–no-create-db/–no-create-info</td><td style="text-align:center"></td><td style="text-align:center">禁止生成创建数据库语句</td></tr><tr><td style="text-align:center">–force</td><td style="text-align:center"></td><td style="text-align:center">当出现错误时仍然继续备份操作</td></tr><tr><td style="text-align:center">–default-character-set</td><td style="text-align:center"></td><td style="text-align:center">指定默认字符集</td></tr><tr><td style="text-align:center">–add-locks</td><td style="text-align:center"></td><td style="text-align:center">备份数据库表时锁定数据库表</td></tr></tbody></table><h4 id="%E7%94%A8%E6%88%B7" tabindex="-1">用户</h4><p>用户权限是非常敏感的一类操作，需要非常小心的使用，所以这里只介绍修改密码的常用操作，其余操作请在完整了解过MySQL权限系统之后再作尝试<br />您可以<a href="https://blog.csdn.net/weixin_44826356/article/details/108730250" target="_blank">参考这篇文章 – Mysql用户与权限操作</a>系统学习MySQL用户与权限</p><pre><code class="language-sql"># 修改用户密码UPDATE USER SET authentication_string=passworD(&quot;你的新密码&quot;) WHERE USER=&#39;root&#39;;# =======================================================# 在MySQL5.7中，mysql.user表中已不再包含Password字段，# 而是使用plugin和authentication_string字段保存用户身份验证的信息# =======================================================</code></pre><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2021/2021-11/mysql-rookie.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[详解Threejs中的光源对象]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/threejs-light" />
                <id>tag:https://fx67ll.xyz,2021-12-02:threejs-light</id>
                <published>2021-12-02T10:41:24+08:00</published>
                <updated>2025-07-02T11:27:14+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h2 id="%E5%85%89%E6%BA%90%E7%9A%84%E5%88%86%E7%B1%BB" tabindex="-1">光源的分类</h2><ol><li><code>AmbientLight</code>（环境光），<code>PointLight</code>（点光源），<code>SpotLight</code>（聚光源） 和 <code>DirectionalLight</code>（平行光）是基础光源</li><li><code>HemisphereLight</code>（半球光源），<code>AreaLight</code>（区域光源），<code>LensFlare</code>（镜头光晕） 是有特殊用途的光源</li></ol><h2 id="%E5%8D%8A%E7%90%83%E5%85%89%E6%BA%90-hemispherelight" tabindex="-1">半球光源 HemisphereLight</h2><ul><li>半球光直接放置于场景之上，光照颜色从天空光线颜色颜色渐变到地面光线颜色</li><li>半球光不能投射阴影</li><li>半球光可以创建出更加贴近自然的户外光照效果，就是为了模拟在户外场景中的反光效果</li></ul><h3 id="%E5%8D%8A%E7%90%83%E5%85%89%E6%BA%90%E7%9A%84%E5%BA%94%E7%94%A8" tabindex="-1">半球光源的应用</h3><p>如果不使用 <code>THREE.HemisphereLight</code>，要模拟户外光照，通常是创建一个 <code>THREE.DirectionalLight</code> 来模拟太阳光，并且可能再添加一个 <code>THREE.AmbientLight</code> 来为场景提供基础色</p><h3 id="%E5%8D%8A%E7%90%83%E5%85%89%E6%BA%90%E7%9A%84%E5%B8%B8%E7%94%A8%E5%B1%9E%E6%80%A7" tabindex="-1">半球光源的常用属性</h3><ul><li>color：从天空发出的光线的颜色</li><li>groundColor：从地面发出的光线的颜色</li><li>intensity：光源照射的强度。默认值为：1。</li><li>position：光源在场景中的位置。默认值为：(0, 100, 0)</li><li>visible：设为 ture（默认值），光源就会打开。设为 false，光源就会关闭。</li></ul><h3 id="%E5%8D%8A%E7%90%83%E5%85%89%E6%BA%90%E7%9A%84%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" tabindex="-1">半球光源的代码示例</h3><pre><code class="language-">var hemiLight = new THREE.HemisphereLight(天空的反光颜色,地面的反光颜色,光的强度)// 这个也是默认位置hemiLight.position.set(0, 100, 0)scene.add(hemiLight)</code></pre><h2 id="%E7%8E%AF%E5%A2%83%E5%85%89-ambientlight" tabindex="-1">环境光 AmbientLight</h2><ul><li>环境光是没有特定方向的光源，会均匀的照亮场景中的所有物体，主要是均匀整体改变Threejs物体表面的明暗效果，这一点和具有方向的光源不同，比如点光源可以让物体表面不同区域明暗程度不同</li><li>环境光影响整个场景，它的光线没有特定来源但是又无处不在，它不能影响阴影生成，因为它没有方向，并且不能作为唯一光源，使用其他光源的同时使用 <code>THREE.AmbientLight</code>，目的是弱化阴影和添加一些颜色</li></ul><h3 id="%E7%8E%AF%E5%A2%83%E5%85%89%E7%9A%84%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" tabindex="-1">环境光的代码示例</h3><pre><code class="language-">// THREE.AmbientLight不需要指定位置，只需要指定颜色（十六进制）var ambientLight = new THREE.AmbientLight(0x0c0c0c)scene.add(ambientLight)</code></pre><h3 id="%E7%8E%AF%E5%A2%83%E5%85%89%E5%92%8C%E5%8D%8A%E7%90%83%E5%85%89%E6%BA%90%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">环境光和半球光源的区别</h3><ul><li>环境光分为 <code>THREE.AmbientLight</code> &amp; <code>THREE.HemisphereLight</code></li><li><code>THREE.AmbientLight</code> 物体明暗对比无法呈现</li><li><code>THREE.HemisphereLight</code> 物体明暗对比比较明显</li><li>如果想模拟真实世界，建议用<code>THREE.HemisphereLight</code>，如果对三维的展现不是特别苛刻，可以用 <code>THREE.AmbientLight</code> 和 <code>THREE.DirectionalLight</code></li></ul><h2 id="%E7%82%B9%E5%85%89%E6%BA%90-pointlight" tabindex="-1">点光源 PointLight</h2><ul><li>点光源就像生活中的白炽灯，光线沿着发光核心向外发散，同一平面的不同位置与点光源光线入射角是不同的，点光源照射下，同一个平面不同区域是呈现出不同的明暗效果</li><li>和环境光不同，环境光不需要设置光源位置，而点光源需要设置位置属性<code>.position</code>，光源位置不同，物体表面被照亮的面不同，远近不同因为衰减明暗程度不同</li></ul><h3 id="%E7%82%B9%E5%85%89%E6%BA%90%E7%9A%84%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" tabindex="-1">点光源的代码示例</h3><pre><code class="language-">var point = new THREE.PointLight(0xffffff)//设置点光源位置，改变光源的位置point.position.set(400, 200, 300)scene.add(point)</code></pre><h2 id="%E5%B9%B3%E8%A1%8C%E5%85%89-directionallight" tabindex="-1">平行光 DirectionalLight</h2><ul><li>平行光顾名思义光线平行，对于一个平面而言，平面不同区域接收到平行光的入射角一样</li><li>点光源因为是向四周发散，所以设置好位置属性<code>.position</code>就可以确定光线和物体表面的夹角</li><li>对于平行光而言，主要是确定光线的方向，光线方向设定好了，光线的与物体表面入射角就确定了，仅仅设置光线位置是不起作用的</li><li>在三维空间中为了确定一条直线的方向只需要确定直线上两个点的坐标即可，所以平行光提供了位置<code>.position</code>和目标<code>.target</code>两个属性来一起确定平行光方向</li><li>目标<code>.target</code>的属性值可以是场景中任何一个三维模型对象，比如一个网格模型Mesh，平行光会通过自身位置属性<code>.position</code>和<code>.target</code>表示的物体的位置属性<code>.position</code>计算出来</li><li>平行光如果不设置<code>.position</code>和<code>.target</code>属性，光线默认从上往下照射，也就是可以认为<code>(0,1,0)</code>和<code>(0,0,0)</code>两个坐标确定的光线方向</li><li>注意一点平行光光源的位置属性<code>.position</code>并不表示平行光从这个位置向远处照射，<code>.position</code>属性只是用来确定平行光的照射方向，平行光你可以理解为太阳光，从无限远处照射过来</li></ul><h3 id="%E5%B9%B3%E8%A1%8C%E5%85%89%E7%9A%84%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" tabindex="-1">平行光的代码示例</h3><pre><code class="language-">var directionalLight = new THREE.DirectionalLight(0xffffff, 1)// 设置光源的方向：通过光源position属性和目标指向对象的position属性计算directionalLight.position.set(80, 100, 50)// 方向光指向对象网格模型mesh，可以不设置，默认的位置是0,0,0directionalLight.target = meshscene.add(directionalLight)</code></pre><h2 id="%E8%81%9A%E5%85%89%E6%BA%90-spotlight" tabindex="-1">聚光源 SpotLight</h2><ul><li>聚光源可以认为是一个沿着特定方会逐渐发散的光源，照射范围在三维空间中构成一个圆锥体</li><li>通过属性<code>.angle</code>可以设置聚光源发散角度，聚光源照射方向设置和平行光光源一样是通过位置<code>.position</code>和目标<code>.target</code>两个属性来实现</li></ul><h3 id="%E8%81%9A%E5%85%89%E6%BA%90%E7%9A%84%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" tabindex="-1">聚光源的代码示例</h3><pre><code class="language-">var spotLight = new THREE.SpotLight(0xffffff)spotLight.position.set(200, 200, 200)spotLight.target = mesh// 设置聚光光源发散角度spotLight.angle = Math.PI / 6scene.add(spotLight)//光对象添加到scene场景中</code></pre><h2 id="%E4%BB%80%E4%B9%88%E6%98%AF%E5%85%89%E6%8A%95%E5%BD%B1" tabindex="-1">什么是光投影</h2><p>在具有方向光源的作用下，物体会形成阴影投影效果</p><h3 id="%E5%A6%82%E4%BD%95%E5%85%89%E6%8A%95%E5%BD%B1%E8%AE%A1%E7%AE%97" tabindex="-1">如何光投影计算</h3><p>Three.js物体投影模拟计算主要设置三部分</p><ol><li>一个是设置产生投影的模型对象</li><li>一个是设置接收投影效果的模型</li><li>最后一个是光源对象本身的设置，光源如何产生投影</li></ol><h3 id="%E5%B9%B3%E8%A1%8C%E5%85%89%E6%8A%95%E5%BD%B1%E8%AE%A1%E7%AE%97%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" tabindex="-1">平行光投影计算代码示例</h3><pre><code class="language-">// 创建方向光光源var directionalLight = new THREE.DirectionalLight(0xffffff, 1)directionalLight.position.set(60, 100, 40)scene.add(directionalLight)// 设置用于计算阴影的光源对象directionalLight.castShadow = true// 设置计算阴影的区域，最好刚好紧密包围在对象周围，如果计算阴影的区域过大：模糊，如果过小：看不到或显示不完整directionalLight.shadow.camera.near = 0.5directionalLight.shadow.camera.far = 300directionalLight.shadow.camera.left = -50directionalLight.shadow.camera.right = 50directionalLight.shadow.camera.top = 200directionalLight.shadow.camera.bottom = -100// 设置mapSize属性可以使阴影更清晰，不那么模糊// directionalLight.shadow.mapSize.set(1024,1024)</code></pre><h3 id="%E8%81%9A%E5%85%89%E6%BA%90%E6%8A%95%E5%BD%B1%E8%AE%A1%E7%AE%97%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" tabindex="-1">聚光源投影计算代码示例</h3><pre><code class="language-">// 创建聚光光源var spotLight = new THREE.SpotLight(0xffffff)spotLight.position.set(50, 90, 50)spotLight.angle = Math.PI /6scene.add(spotLight)// 设置用于计算阴影的光源对象spotLight.castShadow = true// 设置计算阴影的区域，注意包裹对象的周围spotLight.shadow.camera.near = 1spotLight.shadow.camera.far = 300spotLight.shadow.camera.fov = 20</code></pre><h3 id="%E5%85%89%E6%8A%95%E5%BD%B1%E8%AE%A1%E7%AE%97%E7%9A%84%E5%B8%B8%E7%94%A8%E5%B1%9E%E6%80%A7" tabindex="-1">光投影计算的常用属性</h3><ul><li>模型<code>.castShadow</code>属性，<code>.castShadow</code>属性值是布尔值，默认<code>false</code>，用来设置一个模型对象是否在光照下产生投影效果</li><li>模型<code>.receiveShadow</code>属性，<code>.receiveShadow</code>属性值是布尔值，默认<code>false</code>，用来设置一个模型对象是否在光照下接受其它模型的投影效果</li><li>光源<code>.castShadow</code>属性，如果属性设置为<code>true</code>， 光源将投射动态阴影，警告: 这需要很多计算资源，需要调整以使阴影看起来正确</li><li>光源<code>.shadow</code>属性<blockquote><p>平行光<code>DirectionalLight</code>的<code>.shadow</code>属性值是平行光阴影对象<code>DirectionalLightShadow</code><br />聚光源<code>SpotLight</code>的<code>.shadow</code>属性值是聚光源阴影对象<code>SpotLightShadow</code></p></blockquote></li><li>阴影对象基类<code>LightShadow</code><blockquote><p><code>LightShadow</code>属性<code>.camera</code>，观察光源的相机对象，从光的角度来看，以相机对象的观察位置和方向来判断，其他物体背后的物体将处于阴影中<br /><code>LightShadow</code>属性<code>.mapSize</code>，定义阴影纹理贴图宽高尺寸的一个二维向量Vector2，<br />较高的值会以计算时间为代价提供更好的阴影质量，宽高分量值必须<code>是2的幂</code>直到<code>给定设备的WebGLRenderer.capabilities.maxTextureSize</code><br />尽管宽度和高度不必相同 (例如，(512, 1024)是有效的)，默认值为 ( 512, 512 )<br /><code>LightShadow</code>属性<code>.map</code>，该属性的值是<code>WebGL</code>渲染目标对象<code>WebGLRenderTarget</code>，使用内置摄像头生成的深度图，超出像素深度的位置在阴影中，在渲染期间内部计算</p></blockquote></li></ul><p><a href="http://www.webgl3d.cn/Three.js/" target="_blank">参考文档一 ———— Threejs光源对象</a><br /><a href="https://blog.csdn.net/huhuan123456/article/details/109217496" target="_blank">参考文档二 ———— HemisphereLight和AmbientLight 区别</a><br /><a href="https://threejs.org/examples/#webgl_lights_physical" target="_blank">参考文档三 ———— Threejs官方示例</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llThree/blob/master/three-blog/2021/2021-12/three-light.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[了解Threejs中的Clock对象以及简单应用]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/threejs-clock" />
                <id>tag:https://fx67ll.xyz,2021-12-02:threejs-clock</id>
                <published>2021-12-02T10:40:05+08:00</published>
                <updated>2025-07-02T11:27:10+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="%E4%BB%80%E4%B9%88%E6%98%AFclock%E5%AF%B9%E8%B1%A1" tabindex="-1">什么是Clock对象</h3><p>如果你对 JavaScript 有一定了解，那么 JavaScript 的时间对象 Date 你一定不陌生，Clock 本质上就是对 Date 进行封装，提供了一些方法和属性</p><p>当你通过 Threejs 编写一些和时间相关程序时候，不用在对 Date 进行封装，直接调用 Clock 对象的方法和属性即可</p><h3 id="clock%E5%AF%B9%E8%B1%A1%E7%9A%84%E4%B8%BB%E8%A6%81%E5%B1%9E%E6%80%A7%E5%92%8C%E6%96%B9%E6%B3%95" tabindex="-1">Clock对象的主要属性和方法</h3><ul><li>属性<code>.autoStart</code>，Boolean，默认值是<code>true</code>，如果设置为<code>true</code>，则在第一次<code>update</code> 时开启时钟 Clock</li><li>属性<code>.startTime </code>，Float，存储时钟 Clock 最后一次调用<code>.start()</code>, <code>.getElapsedTime()</code>或<code>.getDelta()</code>方法的时间</li><li>属性<code>.elapsedTime </code>，Float，保存时钟 Clock 运行的总时长</li><li>属性<code>.running </code>，Boolean，判断时钟 Clock 是否在运行</li><li>方法<code>.start()</code>，启动时钟，同时将<code>startTime</code>和<code>oldTime</code>设置为当前时间，设置<code>elapsedTime</code>为 0，并且设置<code>running</code>为<code>true</code></li><li>方法<code>.stop()</code>，停止时钟，同时将<code>oldTime</code>设置为当前时间</li><li>方法<code>.getElapsedTime()</code>，获取自时钟启动后的秒数，摒弃将<code>oldTime</code>设置为当前时间，如果<code>autoStart</code>设置为<code>true</code>且时钟并未运行，则该方法同时启动时钟</li><li>方法<code>.getDelta()</code>，获取自<code>oldTime</code>设置后到当前的秒数，同时将<code>oldTime</code>设置为当前时间，如果<code>autoStart</code>设置为<code>true</code>且时钟并未运行，则该方法同时启动时钟</li></ul><h3 id="%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95%EF%BC%9Agetdelta()" tabindex="-1">常用方法：getDelta()</h3><ul><li>获得前后两次执行该方法的时间间隔</li><li>假设你执行一次<code>.getDelta ()</code>方法，再执行一次<code>.getDelta ()</code>方法，第二次执行<code>.getDelta ()</code>方法时候，可以返回上次调用该方法到本次调用之间的时间间隔，返回间隔时间单位是秒</li></ul><h3 id="%E7%AE%80%E5%8D%95%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%EF%BC%9A%E5%AF%B9-threejs-%E6%B8%B2%E6%9F%93%E6%96%B9%E5%BC%8F%E7%9A%84%E7%90%86%E8%A7%A3" tabindex="-1">简单的应用场景：对 Threejs 渲染方式的理解</h3><ul><li>Threejs渲染器的渲染方法<code>.render()</code>每执行一次就得到一帧图像，渲染效果也就是图像会显示在 Canvas 画布上</li><li>如果一个三维场景是不停变化的，肯定要周期性调用执行<code>.render()</code>方法，更新canvas画布显示内容，一帧帧图像随着时间变化，这样就展现出来一个动画效果</li><li>为了周期性执行渲染器渲染方法<code>.render()</code>，一般通过浏览器的API<code>window.requestAnimationFrame</code>实现，浏览器会控制渲染频率</li><li>一般性能理想的情况下，每秒s渲染60次左右，在实际的项目中，如果需要渲染的场景比较复杂，一般都会低于60，也就是渲染的两帧时间间隔大于16.67ms</li><li>代码示例：</li></ul><pre><code class="language-">// 创建一个时钟对象Clockvar clock = new THREE.Clock();// 创建渲染函数function render() {//执行渲染方法，渲染出来一帧图像renderer.render(scene, camera);//周期性执行渲染函数requestAnimationFrame(render);//clock.getDelta()方法获得两帧的时间间隔，返回时间单位：秒var T = clock.getDelta();console.log(&#39;两帧渲染时间间隔&#39;,T*1000+&#39;毫秒&#39;);console.log(&#39;查看每秒渲染频率&#39;,1/T);}render();</code></pre><h3 id="%E5%85%B3%E4%BA%8Erequestanimationframe()%E6%96%B9%E6%B3%95" tabindex="-1">关于<code>requestAnimationFrame()</code>方法</h3><ul><li><code>window.requestAnimationFrame(callback)</code>告诉浏览器——你希望执行一个动画，并且要求浏览器在下次重绘之前调用指定的回调函数更新动画</li><li>该方法需要传入一个回调函数作为参数，该回调函数会在浏览器下一次重绘之前执行</li><li>当你准备更新动画时你应该调用此方法，这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)</li><li>回调函数执行次数通常是每秒60次，但在大多数遵循W3C建议的浏览器中，回调函数执行次数通常与浏览器屏幕刷新次数相匹配</li><li>为了提高性能和电池寿命，因此在大多数浏览器里，当<code>requestAnimationFrame()</code>运行在后台标签页或者隐藏的<code>&lt;iframe&gt;</code>里时，<code>requestAnimationFrame()</code>会被暂停调用以提升性能和电池寿命</li><li>参数<blockquote><p><code>callback</code>，下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)<br />该回调函数会被传入<code>DOMHighResTimeStamp</code>参数，该参数与<code>performance.now()</code>的返回值相同，它表示<code>requestAnimationFrame()</code>开始去执行回调函数的时刻</p></blockquote></li><li>返回值<blockquote><p>一个<code>long</code>整数，请求 ID，是回调列表中唯一的标识<br />是个非零值，没别的意义，你可以传这个值给<code>window.cancelAnimationFrame()</code>以取消回调函数</p></blockquote></li></ul><h3 id="%E4%BD%BF%E7%94%A8setinterval()%E5%92%8Crequestanimationframe()%E6%96%B9%E6%B3%95%E7%BB%98%E5%88%B6%E7%9A%84%E4%BC%98%E5%8A%A3" tabindex="-1">使用<code>setInterval()</code>和<code>requestAnimationFrame()</code>方法绘制的优劣</h3><ul><li>当然使用<code>setInterval()</code>方法可以实现动画效果但是，<code>setInterval()</code>方法有一定的缺点<blockquote><p><code>setInterval()</code>方法，不考虑浏览器中发生的事情，如果你正在浏览其他页面，这个函数仍然会每隔几毫秒就会被调用一次，<br />除此之外，<code>setInterval()</code>方法并没有跟显示器的重画同步，着可能会导致较高的CPU使用，降低系统效率。 —《Three.js 开发指南》</p></blockquote></li><li>使用<code>requestAnimationFrame()</code>函数即可解决上述问题，详情看上方<code>requestAnimationFrame()</code>函数描述，这个函数的时间间隔是浏览器定义的，我们可以在指定的函数里面实现绘画操作</li></ul><h3 id="%E5%9C%A8vue%E4%B8%AD%E4%BD%BF%E7%94%A8requestanimationframe()%E6%96%B9%E6%B3%95" tabindex="-1">在vue中使用<code>requestAnimationFrame()</code>方法</h3><p>一定要传入函数名而不是带上立即执行符号，如<code>requestAnimationFrame(this.animate)</code>即可</p><p><a href="http://www.yanhuangxueyuan.com/doc/Three.js/Clock.html" target="_blank">参考文档 ———— Three.js Clock</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llThree/blob/master/three-blog/2021/2021-12/three-clock.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[2021年11月的最后一天写点什么吧]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/2021-nian-11-yue-de-zui-hou-yi-tian-xie-dian-shen-me-ba" />
                <id>tag:https://fx67ll.xyz,2021-11-30:2021-nian-11-yue-de-zui-hou-yi-tian-xie-dian-shen-me-ba</id>
                <published>2021-11-30T23:39:25+08:00</published>
                <updated>2025-08-12T14:36:17+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h3 id="code">Code</h3><p>今年也算是人生中比较关键转折的一年了吧，首先正式步入了25岁，距离30岁只剩5年！</p><p>心里只有满满的危机感，只能靠技术吃饭的我，技术是否真的可靠呢？是否真的有解决重难点问题的能力？是否能获得别人的信任？</p><p>我似乎没有哪一点有十足的把握，不过好在今年年初开始我给自己列一些今年的学习清单，12月我完成任务的收尾之后来做一次盘点</p><p>所以明年，乃至未来，我都要继续保持这样的习惯，学无止境，人无远虑，必有近忧</p><h3 id="home">Home</h3><p>今年在家人的帮助下，我在南京的远郊买了房子，未来三十年都要为这套房子还债了，说实话压力蛮大，但是男人不能轻易放弃，希望能够多挣些money，早日还完</p><p>当然，有房子了也是开心的，毕竟之后不用再租房，有了自己房子就会有自己的生活，位置偏僻，后面买辆电车，通勤问题不大</p><h3 id="car">Car</h3><p>今年终于考完了驾照，去年报名考试，陆陆续续学习考试了一年多才走完整个流程，确实佛系</p><p>不过，考完的第二天，我就开始琢磨买一个手动挡的车子练手，不然C1的驾照学完真的一点卵用没有啊</p><p>所以，人生的第一辆车，一款11年的手动超值小雨燕被我买下了，让我惊喜的是，他的音响真的比车还值</p><p>当然，练手的车之后，还是希望未来能早日入手自己梦想中的车吧，这个以后再说</p><h3 id="job">Job</h3><p>今年也选择了转岗，虽然原来的职位继续发展也是不错的，但是我这个人对代码着实感兴趣，总希望多多涉猎</p><p>作出这样的决定，压力还是有的，不过幸好之前有所库存，压力还算可以承受，家人也支持我的决定，谢谢老爹老妈，我爱你们</p><h3 id="friend">Friend</h3><p>今年还认识一堆新朋友，希望未来能够保持联系成为挚友，他们都很有人格魅力，十分的吸引人，开心ヽ(✿ﾟ▽ﾟ)ノ</p><h3 id="this-year-">This Year ~</h3><p>今年......<br />其实还有一堆可以记录的事，但是我并不想一一罗列了，总之是人生中很精彩的一年了，未来怎么样也许就在这个转折点之中了<br />希望自己保持身体健康，继续做自己觉得开心有意义的事 (๑•̀ㅂ•́)و✧</p><p>Code is my life~<br />fx67ll</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[详解在Linux中安装配置MongoDB]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/mongodb-install-linux" />
                <id>tag:https://fx67ll.xyz,2021-11-22:mongodb-install-linux</id>
                <published>2021-11-22T17:14:20+08:00</published>
                <updated>2025-07-03T10:39:55+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>最近在整理自己私人服务器上的各种阿猫阿狗，正好就顺手详细记录一下清理之后重装的步骤，今天先写点数据库的内容，关于在<code>Linux</code>中安装配置<code>MongoDB</code></p><p>说实话为什么会装<code>MongoDB</code>呢，因为之前因为公司需要做点<code>Nodejs</code>的中间件，我顺手玩了一下<code>MongoDB</code>的<code>CRUD</code>，文档型数据库还是挺有意思的</p><h3 id="%E5%AE%89%E8%A3%85%E7%8E%AF%E5%A2%83" tabindex="-1">安装环境</h3><p>CentOS7 + MongoDB4.4</p><h3 id="%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E5%8C%85" tabindex="-1">下载安装包</h3><p><a href="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.4.4.tgz" target="_blank">mongodb-4.4.4 版本下载地址（点击链接直接下载）</a></p><h3 id="%E6%93%8D%E4%BD%9C%E6%AD%A5%E9%AA%A4" tabindex="-1">操作步骤</h3><ol><li>利用 xFtp 上传 <code>mongodb.gz</code> 包至安装目录下，我的目录是 <code>/usr/soft/sort</code></li><li>解压安装包至指定目录下，我的是同目录下的install文件夹<blockquote><p><code>tar -zxvf /usr/soft/sort/mongodb-linux-x86_64-rhel70-4.4.4.tgz -C /usr/soft/install/</code></p></blockquote></li><li>配置环境变量<blockquote><p><code>vim /etc/profile</code></p></blockquote></li><li>在文件的最后一行添加如下内容<blockquote><p>按 <code>i</code> 开始修改（注意 <code>s</code> 会删除当前选中字符）<br /><code>export PATH=$PATH:/usr/soft/install/mongodb-linux-x86_64-rhel70-4.4.4/bin</code><br />按 <code>esc</code> 停止编辑，按 <code>:</code> 开始输入，输入 <code>wq</code> 保存并退出<br />之前都会用一个别名来拼接地址，其实直接写完整地址也可以，<code>$PATH</code> 应该是代指之前存有的 <code>PATH变量</code></p></blockquote></li><li>输入 <code>source /etc/profile</code> ，无报错立即生效</li><li>创建数据存放文件夹和日志记录文件夹，为后面的配置文件使用<blockquote><p>在主目录下创建 <code>/data/db</code> 来存放数据<br />在主目录下创建 <code>logs</code> 来存放日志</p></blockquote></li><li>创建运行时使用的配置文件<blockquote><p>在主目录下进入bin目录 <code>cd /bin</code><br />创建配置文件 <code>vim mongodb.conf</code><br />输入以下配置（一定要写完整地址，教程上面是相对地址，结果我启动的时候一直报配置错误）<br /><code>dbpath = /usr/soft/install/mongodb-linux-x86_64-rhel70-4.4.4/data/db  # 数据文件存放目录</code><br /><code>logpath = /usr/soft/install/mongodb-linux-x86_64-rhel70-4.4.4/logs  # 日志文件存放目录</code><br /><code>port = 27017  # 端口</code><br /><code>fork = true  # 以守护程序的方式启用，即在后台运行</code><br /><code># auth=true  # 需要认证，如果放开注释，就必须创建MongoDB的账号，使用账号与密码才可远程访问，第一次安装建议注释</code><br /><code>bind_ip = 0.0.0.0  # 允许远程访问，或者直接注释，127.0.0.1是只允许本地访问</code><br />注意如果不创建账号，是可以直连数据库的，但是创建了账号之后是不能直连的必须要带账号密码才可以连接，例如下面这样<br /><code>mongodb://root:******@xxx.xxx.xxx.xxx:27017/test?authSource=admin&amp;readPreference=primary&amp;ssl=false</code><br />问号后面内容后期了解清楚，之前不加一直无法连接上<br />注意：注释符号 <code>#</code> 和数据之间必须是一个空格</p></blockquote></li><li>测试运行和关闭数据库<blockquote><p>在主目录下进入bin目录 <code>cd /bin</code><br />启动 <code>./mongod -f mongodb.conf</code><br />关闭 <code>pkill mongod</code>（教程介绍了三种方法，目前我只有这一种命令成功了）<br />检查端口是否已经被占用 <code>netstat -nltp|grep 27017</code> 或者 <code>top</code></p></blockquote></li><li>相关错误提示<blockquote><p><code>child process failed,existed with error number 1</code> 之类的错误是配置文件写错，之前就是相对地址而不是全地址导致一直报这个错没有成功运行<br /><code>Mongodb enable authentication</code> 开启了权限或者是创建了账户密码，就需要使用用户名密码连接登录，裸连会直接报这个没有权限的错误</p></blockquote></li></ol><p><a href="https://blog.csdn.net/yzh_1346983557/article/details/81735755" target="_blank">参考资料一 ———— Linux安装、运行MongoDB</a><br /><a href="https://www.jb51.net/article/119514.htm" target="_blank">参考资料二 ———— 在Linux服务器中配置mongodb环境的步骤</a><br /><a href="https://blog.csdn.net/Dn1115680109/article/details/88754067" target="_blank">参考资料三 ———— ERROR: child process failed, exited with error number 1</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/blob/master/serve-blog/2021/2021-11/linux-install-mongodb.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[详解在Linux中安装配置MySQL]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/mysql-install-linux" />
                <id>tag:https://fx67ll.xyz,2021-11-22:mysql-install-linux</id>
                <published>2021-11-22T17:12:31+08:00</published>
                <updated>2025-07-03T10:40:00+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>近在整理自己私人服务器上的各种阿猫阿狗，正好就顺手详细记录一下清理之后重装的步骤，今天先写点数据库的内容，关于在<code>Linux</code>中安装配置<code>MySQL</code></p><h3 id="%E5%AE%89%E8%A3%85%E7%8E%AF%E5%A2%83" tabindex="-1">安装环境</h3><p>CentOS7 + MySQL5.7</p><h3 id="%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E5%8C%85" tabindex="-1">下载安装包</h3><p><a href="https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5.7.26-linux-glibc2.12-x86_64.tar.gz" target="_blank">mysql-5.7.26 版本下载地址（点击链接直接下载）</a></p><h3 id="%E6%93%8D%E4%BD%9C%E6%AD%A5%E9%AA%A4" tabindex="-1">操作步骤</h3><ol><li>利用 xFtp 上传 <code>mysql.gz</code> 包至安装目录下，我的目录是 <code>/usr/soft/sort</code></li><li>解压安装包至指定目录下，我的是同目录下的install文件夹<blockquote><p><code>tar -zxvf /usr/soft/sort/mysql-5.7.26-linux-glibc2.12-x86_64.tar.gz -C /usr/soft/install/</code></p></blockquote></li><li>创建组<blockquote><p><code>groupadd mysql</code></p></blockquote></li><li>创建用户<blockquote><p><code>useradd -r -g mysql mysql</code></p></blockquote></li><li>将安装目录所有者及所属组改为mysql<blockquote><p><code>chown -R mysql.mysql /usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64</code></p></blockquote></li><li>进入mysql目录并创建data文件夹用于存放数据库表之类的数据<blockquote><p><code>cd /usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64</code><br /><code>mkdir data</code></p></blockquote></li><li>准备初始化，首先要安装依赖库libaio<blockquote><p><code>yum install libaio</code></p></blockquote></li><li>准备初始化，这一步务必记住初始密码，它位于输出日志的末尾（数据库管理员临时密码）<blockquote><p>注意这是一整条命令：<code>/usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64/bin/mysqld --user=mysql --basedir=/usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64/ --datadir=/usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64/data --initialize</code></p><p>我的输出日志示例：<code>20xx-xx-xxTxx:xx:xx.493483Z 1 [Note] A temporary password is generated for root@localhost: 这里是初始的临时密码</code></p></blockquote></li><li>配置系统环境变量<ul><li>编辑 <code>vim /etc/profile</code></li><li>添加以下环境变量<blockquote><p><code>export MYSQL_HOME=/usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64</code><br /><code>export PATH=$PATH:$MYSQL_HOME/bin</code></p></blockquote></li><li>更新 <code>source /etc/profile</code></li></ul></li><li>配置mysql配置，这里最好查询一下所有配置的含义，可以参考 <a href="https://www.cnblogs.com/captain_jack/archive/2010/10/12/1848496.html" target="_blank">这篇文章</a><blockquote><p><code>datadir=/usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64/data</code><br /><code>basedir=/usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64</code><br /><code>socket=/tmp/mysql.sock</code>（这行很重要，不然后续socket连接会出问题）<br /><code>user=mysql</code><br /><code>port=3306</code><br /><code>innodb_file_ per_table=1</code><br /><code>character-set-server=utf8</code></p></blockquote></li><li>这里需要操作两个目录，用于配置文件中部分文件的运行，不然直接启动会报错，建议先完成错误解决方案中的代码<ul><li>第一个错误<code>mysqld_safe error: log-error set to /var/log/mariadb/mariadb.log</code></li><li>第一个错误解决方案，新建并添加权限<blockquote><p><code>mkdir /var/log/mariadb</code><br /><code>touch /var/log/mariadb/mariadb.log</code><br /><code>chown -R mysql:mysql /var/log/mariadb/</code></p></blockquote></li><li>第二个错误<code>mysqld_safe Directory '/var/lib/mysql' for UNIX socket file don't exists.</code></li><li>第二个错误解决方案，新建并添加权限<blockquote><p><code>mkdir /var/lib/mysql</code><br /><code>chmod 777 /var/lib/mysql</code></p></blockquote></li><li><a href="https://blog.csdn.net/qq_34218345/article/details/106951035" target="_blank">参考文档一</a></li><li><a href="https://blog.csdn.net/qq_32331073/article/details/76229420" target="_blank">参考文档二</a></li></ul></li><li>将mysql加入服务<blockquote><p><code>cp /usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64/support-files/mysql.server /etc/init.d/mysql</code></p></blockquote></li><li>设置开机启动<blockquote><p><code>chkconfig mysql on</code></p></blockquote></li><li>添加软连接<blockquote><p><code>ln -s /usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64/support-files/mysql.server /etc/init.d/mysql</code></p><p><code>ln -s /usr/soft/install/mysql-5.7.26-linux-glibc2.12-x86_64/bin/mysql /usr/bin/mysql</code></p></blockquote></li><li>启动mysql<blockquote><p><code>service mysql start</code></p></blockquote></li><li>使用初始密码登录<ul><li>执行 <code>mysql -u root -p</code>（socket连接）</li><li>或者执行 <code>mysql -u root -h 127.0.0.1 -p</code>（本地连接）</li><li>输入密码，可以直接去前面保存的初始密码复制过来</li></ul></li><li>修改初始密码<ul><li><code>use mysql;</code>（注意mysql语句使用英文<code>;</code>结束！！！）</li><li><code>mysql&gt; update user set authentication_string=passworD(&quot;你的新密码&quot;) where user='root';</code>（mysql5.7及以上版本需要使用<code>authentication_string</code>字段来修改密码，有些博文并未提及，需要注意！！！）</li><li>这个也可修改密码，效果同上 <code>set password=password(&quot;你的新秘密&quot;);</code></li><li>重新加载权限表 <code>flush privileges;</code></li><li>退出mysql <code>exit;</code></li></ul></li></ol><p><a href="https://www.cnblogs.com/shenjianping/p/10984540.html" target="_blank">参考资料一 ———— linux下mysql的安装与使用</a><br /><a href="https://blog.csdn.net/weixin_42734930/article/details/81743047" target="_blank">参考资料二 ———— linux 安装 mysql简单教程</a><br /><a href="https://www.cnblogs.com/captain_jack/archive/2010/10/12/1848496.html" target="_blank">参考资料三 ———— linux下mysql配置文件my.cnf详解</a><br /><a href="https://blog.csdn.net/qq_34218345/article/details/106951035" target="_blank">参考资料四 ———— 启动mysql报错mysqld_safe error: log-error set to /var/log/mariadb/mariadb.log</a><br /><a href="https://blog.csdn.net/qq_32331073/article/details/76229420" target="_blank">参考资料五 ———— mysqld_safe Directory ‘/var/lib/mysql‘ for UNIX socket file don‘t exists.</a><br /><a href="https://blog.csdn.net/huanghuizz/article/details/84472590?spm=1001.2014.3001.5502" target="_blank">参考资料六 ———— linux下将mysql加入到环境变量</a><br /><a href="https://www.cnblogs.com/net5x/articles/10033056.html" target="_blank">参考资料七 ———— MySQL–启动和关闭MySQL服务</a><br /><a href="https://www.cnblogs.com/benpao1314/p/11534696.html" target="_blank">参考资料八 ———— mysql报错：You must reset your password using ALTER USER statement before executing this statement.</a><br /><a href="https://www.cnblogs.com/surplus/p/11642773.html" target="_blank">参考资料九 ———— Linux下修改Mysql密码的三种方式</a><br /><a href="https://blog.csdn.net/weixin_34025051/article/details/93185516" target="_blank">参考资料十 ———— 查看MySQL是否在运行</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/blob/master/serve-blog/2021/2021-11/linux-install-mysql.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[解决ip和域名都能够ping通但是启动nginx无法访问网页的问题]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/ip-ping-fix" />
                <id>tag:https://fx67ll.xyz,2021-11-22:ip-ping-fix</id>
                <published>2021-11-22T15:05:36+08:00</published>
                <updated>2025-07-03T10:40:04+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年11月20日</strong></em></p><h3 id="%E8%A7%A3%E5%86%B3%E6%80%9D%E8%B7%AF" tabindex="-1">解决思路</h3><ol><li>最近双11逛西部数码的官网看看有没有什么服务器优惠的时候，发现了可以申请一个一块钱用一整年的SSL证书，立马心动下单了，想想俺也可以用<code>https</code>装装X了哈哈</li><li>不过在部署完证书，并调整nginx代理将初始端口指向<code>443端口</code>之时，突然发现个人站点访问不到了，有点奇怪</li><li>但是，遇到问题先别慌，先检查服务器的运行状态，一切OK，再检查是否能够ping通我的IP和域名，好没问题</li><li>咦这么奇怪的嘛，在我脑子没有转过弯之前，我一直没注意我的防火墙端口只开放到了初始端口，并没有开放<code>443端口</code>，啊，我在搞什么啊</li><li>于是，在<code>/etc/sysconfig/iptables</code>文件中开放<code>443端口</code>，重启防火墙，OK，网页访问正常了</li><li>总结，我真是个大傻X哈哈哈哈哈</li></ol><h3 id="%E5%BC%80%E5%90%AF443%E7%AB%AF%E5%8F%A3%E6%B5%81%E7%A8%8B" tabindex="-1">开启443端口流程</h3><ol><li><code>cd /etc/sysconfig</code>进入该目录，检查是否存储了<code>iptables</code>文件</li><li><code>vim iptables</code>使用<code>vim编辑器</code>修改<code>iptables</code>文件，按下<code>i</code>进入编辑模式</li><li>在初始端口那行下面添加<code>-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT</code>，开放443端口</li><li><code>service iptables restart</code>重启防火墙即可</li></ol><h3 id="liunx%E9%98%B2%E7%81%AB%E5%A2%99%E5%91%BD%E4%BB%A4" tabindex="-1">liunx防火墙命令</h3><p>linux中主要分为旧版的iptables防火墙和新版的firewall防火墙</p><h4 id="iptables%E9%98%B2%E7%81%AB%E5%A2%99" tabindex="-1">iptables防火墙</h4><ol><li>查看防火墙状态  <code>service iptables status</code></li><li>停止防火墙  <code>service iptables stop</code></li><li>启动防火墙  <code>service iptables start</code></li><li>重启防火墙  <code>service iptables restart</code></li><li>永久关闭防火墙  <code>chkconfig iptables off</code></li><li>永久关闭防火墙后重启  <code>chkconfig iptables on</code></li></ol><h4 id="firewall%E9%98%B2%E7%81%AB%E5%A2%99" tabindex="-1">firewall防火墙</h4><ol><li>查看防火墙服务状态  <code>systemctl status firewalld</code></li><li>查看防火墙状态  <code>firewall-cmd --state</code></li><li>停止防火墙  <code>service firewalld stop</code></li><li>启动防火墙  <code>service firewalld start</code></li><li>重启防火墙  <code>service firewalld restart</code></li><li>查看防火墙规则  <code>firewall-cmd --list-all</code></li><li>查看80端口是否开放  <code>firewall-cmd --query-port=80/tcp</code></li><li>开放80端口  <code>firewall-cmd --permanent --add-port=80/tcp</code></li><li>移除80端口  <code>firewall-cmd --permanent --remove-port=80/tcp</code></li><li>开放和移除端口都是对配置文件做出了修改，需要重启防火墙，下面是<code>8/9</code>命令中的参数解析<ul><li><code>firewall-cmd</code>  是<code>linux</code>提供的操作<code>firewall</code>的一个工具</li><li><code>--permanent</code>  表示设置为持久</li><li><code>--add-port</code>  表示添加的端口</li></ul></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llLinux/blob/master/serve-blog/ping-web.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[解决 IDEA 2021.2.3 新建maven项目只有两个archetype项目模板的问题]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/idea-maven-fix" />
                <id>tag:https://fx67ll.xyz,2021-11-22:idea-maven-fix</id>
                <published>2021-11-22T15:00:49+08:00</published>
                <updated>2025-07-02T11:27:55+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年11月19日</strong></em></p><p>最近把我的 IDEA 版本更新到 2021.2.3 了，发生了一个比较有意思的问题，做个小小的记录</p><h3 id="%E6%80%9D%E8%B7%AF%E5%88%86%E6%9E%90" tabindex="-1">思路分析</h3><p>在新的 IDEA 中配置完Maven之后，想要创建Maven项目的时候没有自动加载archetype项目模板的列表了，<br />只会出现两个Kotlin的项目模板。所以出现这个问题的时候我愣了一下，因为老版的可是有几十个模板啊，接着简单思考一下：<br />既然没有出来，说明软件的内部没有内置模板，那么作为一个插件极其丰富的软件，我们是不是可以查找一下是否有这种插件？</p><h3 id="%E8%A7%A3%E5%86%B3%E6%AD%A5%E9%AA%A4" tabindex="-1">解决步骤</h3><ol><li>关闭当前项目，左上角<code>File -&gt; Close Project</code></li></ol><p><img src="https://img-blog.csdnimg.cn/592636e6b15044c5aaa63297ad997172.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAZng2N2xs,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center" alt="关闭项目" /></p><ol start="2"><li>点击欢迎界面左侧的<code>Plugin</code>选项，搜索<code>Maven Archetype Catalogs</code>安装</li></ol><p><img src="https://img-blog.csdnimg.cn/d67375d6cfc346c5b26cad4cea230564.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAZng2N2xs,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center" alt="安装插件" /></p><ol start="3"><li>之后再新建Maven项目的时候应该就没有问题了</li></ol><p><img src="https://img-blog.csdnimg.cn/ccce03ff103445629b6f4517f4be75b6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAZng2N2xs,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center" alt="新建项目" /></p><h4 id="%E5%9C%A8%E6%88%91%E4%BB%AC%E5%B7%A5%E4%BD%9C%E4%B8%AD%E4%BC%9A%E9%81%87%E5%88%B0%E6%97%A0%E6%95%B0%E7%9A%84%E9%97%AE%E9%A2%98%EF%BC%8C%E6%9C%89%E4%BA%9B%E6%97%B6%E5%80%99%EF%BC%8C%E6%89%BE%E5%88%B0%E8%A7%A3%E5%86%B3%E6%80%9D%E8%B7%AF%E5%BE%80%E5%BE%80%E6%83%B3%E7%9D%80%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98%E6%9B%B4%E4%B8%BA%E9%87%8D%E8%A6%81" tabindex="-1">在我们工作中会遇到无数的问题，有些时候，找到解决思路往往想着解决问题更为重要</h4><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2021/2021-11/idea-archetype.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[fx67llQuickEcharts]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/fx67llquickecharts" />
                <id>tag:https://fx67ll.xyz,2021-11-22:fx67llquickecharts</id>
                <published>2021-11-22T14:57:12+08:00</published>
                <updated>2025-07-02T11:26:34+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月27日</strong></em></p><p>A tool to help you use Echarts quickly!</p><p><a href="https://www.npmjs.com/package/fx67ll-quick-echarts" title="npm" target="_blank">npm</a></p><h3 id="%E7%BB%84%E4%BB%B6%E8%AF%B4%E6%98%8E" tabindex="-1">组件说明</h3><ol><li>这本来是一个测试如何发布Vue组件至npm库的测试项目</li><li>做完之后发现很有意思，就稍微完善了一下成为一个简易快速构建Echarts图表的组件</li><li>基本上消灭了我之前关于Echarts渲染DOM的一堆冗余代码</li><li>功能不多后续有想法的话再考虑继续封装，github源码地址在底部，各位自行取用</li></ol><h3 id="%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4" tabindex="-1">使用步骤</h3><ol><li>安装 -&gt; <code>npm install fx67ll-quick-echarts --save</code></li><li>注册 -&gt; <code>main.js</code></li></ol><pre><code class="language-JavaScript">import fx67llQuickEcharts from &#39;fx67ll-quick-echarts&#39;Vue.use(fx67llQuickEcharts)</code></pre><ol start="3"><li>使用 -&gt; <code>.vue</code>，可在 <a href="https://echarts.apache.org/examples/zh/index.html" target="_blank">Echarts官网</a> 查询相关option配置</li></ol><pre><code class="language-">// 属性 chartOption: Echarts option 图表配置项// 事件 @nodeClick: 图表中节点的点击事件，返回值 chartParams 当前节点的具体信息// 事件 @nodeClick: 如果没有点击到节点，返回值为null，可以用来判断是否点击到空白处了// 方法 setHLByDataIndex(dataIndex)：指定图表节点高亮的方法，传入参数 dataIndex 需要高亮的节点下标或下标数组，number/number[]// 方法 setDPByDataIndex(dataIndex)：指定图表节点取消高亮的方法，传入参数 dataIndex 需要取消高亮的节点下标或下标数组，number/number[]&lt;quick-echarts ref=&quot;qe&quot; :chartOption=&quot;chartOption&quot; @nodeClick=&quot;nodeClick&quot;&gt;&lt;/quick-echarts&gt;methods{this.$refs.qe.setHLByDataIndex([0, 1]);this.$refs.qe.setDPByDataIndex([0, 1]);}</code></pre><pre><code class="language-JavaScript">// 返回值 chartParams 的各项属性详细说明{    // 当前点击的图形元素所属的组件名称，    // 其值如 &#39;series&#39;、&#39;markLine&#39;、&#39;markPoint&#39;、&#39;timeLine&#39; 等。    componentType: string,    // 系列类型。值可能为：&#39;line&#39;、&#39;bar&#39;、&#39;pie&#39; 等。当 componentType 为 &#39;series&#39; 时有意义。    seriesType: string,    // 系列在传入的 option.series 中的 index。当 componentType 为 &#39;series&#39; 时有意义。    seriesIndex: number,    // 系列名称。当 componentType 为 &#39;series&#39; 时有意义。    seriesName: string,    // 数据名，类目名    name: string,    // 数据在传入的 data 数组中的 index    dataIndex: number,    // 传入的原始数据项    data: Object,    // sankey、graph 等图表同时含有 nodeData 和 edgeData 两种 data，    // dataType 的值会是 &#39;node&#39; 或者 &#39;edge&#39;，表示当前点击在 node 还是 edge 上。    // 其他大部分图表中只有一种 data，dataType 无意义。    dataType: string,    // 传入的数据值    value: number|Array,    // 数据图形的颜色。当 componentType 为 &#39;series&#39; 时有意义。    color: string,    // 用户自定义的数据。只在 graphic component 和自定义系列（custom series）    // 中生效，如果节点定义上设置了如：{type: &#39;circle&#39;, info: {some: 123}}。    info: *}</code></pre><h3 id="%E7%AE%80%E6%98%93%E7%A4%BA%E4%BE%8B" tabindex="-1">简易示例</h3><pre><code class="language-Vue">&lt;template&gt;&lt;quick-echarts :chartOption=&quot;option&quot; @nodeClick=&quot;nodeClick&quot;&gt;&lt;/quick-echarts&gt;&lt;/template&gt;&lt;script&gt;export default {data() {return {option: {xAxis: {type: &#39;category&#39;,data: [&#39;Mon&#39;, &#39;Tue&#39;, &#39;Wed&#39;, &#39;Thu&#39;, &#39;Fri&#39;, &#39;Sat&#39;, &#39;Sun&#39;]},yAxis: {type: &#39;value&#39;},series: [{data: [150, 230, 224, 218, 135, 147, 260],type: &#39;line&#39;}]}};},methods: {nodeClick(chartParams) {console.log(chartParams);}}};&lt;/script&gt;</code></pre><h3 id="%E5%A5%BD%E7%94%A8%E4%B9%8B%E5%A4%84" tabindex="-1">好用之处</h3><ol><li>新手能非常快速的上手Echarts，只需要去<a href="https://echarts.apache.org/examples/zh/index.html" target="_blank">echarts官网</a>查找需要的option即可，三步即可使用</li><li>对于不需要过多复杂操作，仅做展示的图表，可以直接使用，只需要组装好需要的option传入即可</li><li>封装了自动监听数据变化，图表大小变化自动适应，以及监听了图表点击事件，可用于相关的联动交互</li></ol><h3 id="%E4%B8%8D%E8%B6%B3%E4%B9%8B%E5%A4%84" tabindex="-1">不足之处</h3><ol><li>目前只有一个最基础的Echarts使用组件，对于一些复杂的图表交互操作和事件监听没有封装，未来会逐步添加</li><li>对于一些特定复杂样式没有作出封装，常用的线图柱图饼图会优先做出一定封装，并允许添加复合的样式</li><li>目前是整体组件引用，后续会添加按需加载的支持，提升项目性能</li></ol><h3 id="%E4%BD%BF%E7%94%A8%E7%89%88%E6%9C%AC" tabindex="-1">使用版本</h3><p><code>Vue2.6 + Echarts5.1</code></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llQuickEcharts" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[详解如何用 CSS3 完成 3D transform变换]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/css3-3d-transform" />
                <id>tag:https://fx67ll.xyz,2021-11-22:css3-3d-transform</id>
                <published>2021-11-22T14:52:47+08:00</published>
                <updated>2025-07-02T11:26:39+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月26日</strong></em></p><p><em><strong>Tips：阅读提示！！！</strong></em></p><ol><li>首先，本文针对的是3D transform变换的学习，所以你需要对 <a href="https://www.w3school.com.cn/css/css3_2dtransforms.asp" target="_blank">2D transform变换</a> 有一定的了解</li><li>其次，需要说明的是，代码是一种需要自己不断实践的学科，建议各位在开始学习本篇文档的时候，先创建一个<code>html页面</code>来边读边练，<br />相信这样，一定会给你留下一个非常直观且深刻的印象！</li></ol><h3 id="%E6%9C%AC%E6%96%87%E7%BB%83%E4%B9%A0%E4%B8%80%E4%BA%9B%E5%85%AC%E5%85%B1%E4%BB%A3%E7%A0%81" tabindex="-1">本文练习一些公共代码</h3><p>鉴于本文贴了不少代码来演示，所有这里提前放一个所有演示的公共代码片段</p><pre><code class="language-">// 公共DOM元素，后续均为此写样式表// 舞台容器&lt;div class=&quot;stage&quot;&gt;// 变换元素&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box&quot;&gt;&lt;/div&gt;&lt;/div&gt;// 样式兼容的各个浏览器属性头，有需要的在各自代码中添加  // 下列测试代码使用Chorme运行  -webkit-  /* Safari 和 Chrome */-ms-   /* IE */-moz-   /* Firefox */-o-  /* Opera */</code></pre><h3 id="%E5%AE%9E%E7%8E%B03d%E5%8F%98%E6%8D%A2%E7%9A%84%E5%9F%BA%E7%A1%80-%E2%80%93-%E9%80%8F%E8%A7%86(perspective)" tabindex="-1">实现3D变换的基础 – 透视(perspective)</h3><p>当我们步入<code>CSS3</code>动画殿堂之时，我们一般都会从一些简单动的东西开始学习，比如元素的位移，旋转，放大缩小之类的<code>2D transform变换</code>，<br />那么如何在一个<code>2D</code>的屏幕上实现<code>3D</code>的变换呢，这里我们就先需要有一个概念，就是 <em><strong>透视</strong></em></p><p>从一个最简单的 <em><strong>透视</strong></em> 说起，我们小时候的数学课上，数学老师一定会给我们画一个立体正方形，能够在<code>2D</code>平面上看出<code>3D</code>效果就是 <em><strong>透视</strong></em> 的作用</p><p>用一张直观的图片来说明一下<br /><img src="https://img2020.cnblogs.com/blog/1671116/202110/1671116-20211026203955873-1804901582.jpg" alt="透视正方体" /></p><p>透视的类型有以下几种<br /><img src="https://img2020.cnblogs.com/blog/1671116/202110/1671116-20211026204015660-1073412712.jpg" alt="透视类型" /></p><h4 id="%E9%82%A3%E4%B9%88%E5%9C%A8css3%E4%B8%AD%E5%A6%82%E4%BD%95%E7%94%A8%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0%E9%80%8F%E8%A7%86%E5%91%A2%EF%BC%9F" tabindex="-1">那么在CSS3中如何用代码实现透视呢？</h4><ol><li>主要是通过<code>perspective</code>属性来实现定义透视距离</li><li><code>perspective</code>属性定义<code>3D</code>元素距视图的距离，以像素计，该属性允许您改变<code>3D</code>元素查看<code>3D</code>元素的视图</li><li>透视距离可以这么理解，如果你的显示器宽度是<code>1920px</code>的分辨率，那么<code>2000px</code>的透视距离就近似于，从显示器平面开始，一个显示器宽度的距离</li><li>默认值是<code>0px</code>，可以理解为初始位置</li><li>注意！！！当为元素定义 perspective 属性时，其子元素会获得透视效果，而不是元素本身</li><li>它有两种写法，单个元素时候看似效果相同，但是多元素在同一个舞台上呈现时，就会出现问题了<ul><li>第一种，是写在舞台元素上，定义<code>perspective</code>属性</li><li>第二种，是配合<code>transform</code>属性一起，例如：<code>transform: perspective(600px) rotateY(45deg)</code></li><li>参考下方示例，您可以在自己的demo中测试效果区别，你会发现第二种所有元素都是同一角度的旋转，<br />这是因为第一种透视点是<code>.stage</code>中央，第二种透视点是每一个<code>.box</code>中央</li></ul></li></ol><pre><code class="language-CSS">// 第一种写法.stage {width: 100%;height: 100%;perspective: 1000px;-webkit-perspective: 500px;}.stage .box {width: 100px;height: 100px;margin: 100px 100px;background-color: #55ffff;display: inline-block;    transform: rotateY(65deg);-webkit-transform: rotateY(65deg);}// 第二种写法.stage {width: 100%;height: 100%;}.stage .box {width: 100px;height: 100px;margin: 100px 100px;background-color: #55ffff;display: inline-block;    transform: perspective(1000px) rotateY(65deg);-webkit-transform: perspective(1000px) rotateY(65deg);}</code></pre><h4 id="%E9%80%8F%E8%A7%86%E5%9F%BA%E7%82%B9%E4%BD%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%EF%BC%88perspective-origin%EF%BC%89" tabindex="-1">透视基点位置属性（perspective-origin）</h4><ol><li>既然我我们有透视距离的概念，那么这个透视基点的意思就是，我们所看的舞台或者元素的中心</li><li><code>perspective</code>(透视)属性必须和<code>perspective-origin</code>(透视基点)属性一同使用</li><li><code>perspective-origin: x-axis y-axis</code>，xy可以分别是这些值：<ul><li>left</li><li>right</li><li>center</li><li>length，像素</li><li>%，百分比</li></ul></li><li>举个不恰当的例子，透视基点类似看足球比赛中那个球的位置，因为所有人眼都会盯着场上的足球，你和球的距离理解为透视距离就好，当然这并不恰当，不要在意</li><li>该属性必须与<code>perspective</code>属性一同使用，而且只影响<code>3D</code>转换元素，它可以定义在元素中，并且同<code>perspective</code>属性一样，定义在哪里影响哪里</li><li>默认值是<code>50% 50%</code>，表示 xy 中心位置</li><li>下面我们利用一个示例来看看，你依旧可以通过修改demo中的参数，看到效果的变化</li></ol><pre><code class="language-CSS">// 50% 50% 正好是变换元素的中心点，也是默认值  .stage {width: 100%;height: 100%;perspective: 604px;-webkit-perspective: 604px;// 使用百分比perspective-origin: 50% 50%;-webkit-perspective-origin: 50% 50%;// 使用像素// perspective-origin: 200px 100px;// -webkit-perspective-origin: 200px 100px;// 使用位置// perspective-origin: left center;// -webkit-perspective-origin: left center;// 也可以混用// perspective-origin: 200px 10%;// -webkit-perspective-origin: 200px 10%;}.stage .box {width: 100px;height: 100px;margin: 100px 100px;background-color: #55ffff;display: inline-block;    transform: rotateY(65deg);-webkit-transform: rotateY(65deg);}</code></pre><h4 id="%E9%80%8F%E8%A7%86%E7%9B%B2%E5%8C%BA" tabindex="-1">透视盲区</h4><p>在我们进行3D变换的时候还会遇到透视盲区的问题，比如一个正方形，旋转45°之后，正好和你的视线完全平行，那么这个面你就看不到，这就是视觉盲区<br />你可以利用下方代码在自己的demo中查看一下效果</p><pre><code class="language-CSS">// 我的电脑分辨率是&#96;1920*1080&#96;，透视距离是&#96;604px&#96;// 如果你看不到效果，可以尝试修改旋转角度或透视距离，找一下出现盲区的角度  .stage {width: 100%;height: 100%;perspective: 604px;-webkit-perspective: 604px}.stage .box {width: 100px;height: 100px;margin: 100px 100px;background-color: #55ffff;display: inline-block;    transform: rotateY(65deg);-webkit-transform: rotateY(65deg);}</code></pre><h3 id="%E5%AE%9E%E7%8E%B0%E9%9D%99%E6%80%81%E7%9A%843d%E5%8F%98%E6%8D%A2-%E2%80%93-%E8%BD%AC%E6%8D%A2(transform)" tabindex="-1">实现静态的3D变换 – 转换(transform)</h3><p>相信在2D变换的学习中对于<code>transform</code>属性一定不陌生了，它不仅可以进行<code>2D</code>变换，也可以进行<code>3D</code>变换，只不过需要借助上一个透视的属性，我们才可以在屏幕中看到变换的效果</p><p>先来说明一个三维坐标系的概念，通俗点来说，<code>z轴</code>对应垂直方向，<code>x轴</code>对应前后方向，<code>y轴</code>对应左右方向<br />后续我们所说的绕轴变化，你就可以参考这个图例，想象一下发生的变换<br /><img src="https://img2020.cnblogs.com/blog/1671116/202110/1671116-20211026204047048-690568036.jpg" alt="三维坐标系示意" /></p><p>接下来我们来看看<code>transform</code>支持的变换类型，如果对此您有疑问，可以参考<a href="https://www.w3school.com.cn/cssref/pr_transform.asp" target="_blank">w3school的可测试教程</a>：</p><ol><li>2D类型<blockquote><p>转换（位移）  <code>translate(x,y)</code> / <code>translateX(x)</code> / <code>translateY(y)</code><br />缩放 <code>scale(x,y)</code> / <code>scaleX(x)</code> / <code>scaleY(y)</code><br />旋转 <code>rotate(angle)</code> / <code>rotateX(angle)</code> / <code>rotateY(angle)</code><br />倾斜 <code>skew(x-angle,y-angle)</code> / <code>skewX(angle)</code> / <code>skewY(angle)</code><br /><code>matrix()</code>，把所有 2D 变换方法组合为一个，可接受六个参数，<code>matrix(scaleX(),skewY(),skewX(),scaleY(),translateX(),translateY())</code></p></blockquote></li><li>3D类型<blockquote><p>转换（位移） <code>translate3d(x,y,z)</code> / <code>translateZ(z)</code>，主要是垂直距离的位移<br />缩放 <code>scale3d(x,y,z)</code> / <code>scaleZ(z)</code>，同样是垂直距离的缩放<br />旋转 <code>rotate3d(x,y,z,angle)</code> / <code>rotateX(angle)</code> / <code>rotateY(angle)</code> / <code>rotateZ(angle)</code><br /><code>rotateX</code>/<code>rotateY</code>/<code>rotateZ</code>三种函数分别意思是绕着<code>XYZ</code>轴做旋转运动，<code>angle</code>参数为单位为<code>deg</code>的角度<br /><code>rotate3d</code>函数定义一个变换，它将元素围绕固定轴移动而不使其变形，运动量由指定的角度定义，<br />如果为正，运动将为顺时针，如果为负，则为逆时针，想要了解更多可以参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-function/rotate3d()" target="_blank">这篇文档中的动态示例</a> 理解</p></blockquote></li></ol><p><code>2D</code>变换请参考 <a href="https://www.w3school.com.cn/css/css3_2dtransforms.asp" target="_blank">本文Tips中的链接</a> 或百度教程，下面我们来详细看下实现静态<code>3D</code>变换的代码</p><pre><code class="language-CSS">.stage {width: 100%;height: 100%;perspective: 500px;-webkit-perspective: 500px;perspective-origin: 50% 50%;-webkit-perspective-origin: 50% 50%;}.stage .box {width: 100px;height: 100px;margin: 100px 100px;background-color: #55ffff;display: inline-block;// 这里表示绕Y轴做旋转65度的变换transform: rotate3d(0, 1, 0, 65deg);-webkit-transform: rotate3d(0, 1, 0, 65deg);}</code></pre><h4 id="%E8%BD%AC%E6%8D%A2%E5%9F%BA%E7%82%B9%E4%BD%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%EF%BC%88transform-origin%EF%BC%89" tabindex="-1">转换基点位置属性（transform-origin）</h4><ol><li><code>transform-origin</code>属性允许您改变被转换元素的位置，<code>2D</code>转换元素能够改变元素xy轴，<code>3D</code>转换元素还能改变其z轴</li><li><code>transform-origin: x-axis y-axis z-axis</code>，这定义了视图被置于xyz轴的何处，z值只能是<code>length</code>，xy可以分别是这些值：<ul><li>left</li><li>right</li><li>center</li><li>length，像素</li><li>%，百分比</li></ul></li><li><code>transform-origin</code>属性必须和<code>transform</code>属性一同使用</li><li>默认值是<code>50% 50% 0</code>，表示转换基点位于 xy 中心以及 z轴 0 的位置</li><li>下面我们利用一个示例来看看，你依旧可以通过修改demo中的参数，看到效果的变化</li></ol><pre><code class="language-CSS">.stage {width: 100%;height: 100%;perspective: 500px;-webkit-perspective: 500px;perspective-origin: 50% 50%;-webkit-perspective-origin: 50% 50%;}.stage .box {width: 100px;height: 100px;margin: 100px 100px;background-color: #55ffff;display: inline-block;transform: rotate3d(0, 1, 0, 65deg);-webkit-transform: rotate3d(0, 1, 0, 65deg);// 表示旋转元素的基准点在 x 100px  y 100px  z -900px 位置// 值类型自由使用，不做过多说明，参考上方透视基点属性transform-origin: 100px 100px -900px;-webkit-transform-origin: 100px 100px -900px;}</code></pre><h4 id="%E8%BD%AC%E6%8D%A2%E5%A6%82%E4%BD%95%E5%91%88%E7%8E%B0%E5%B1%9E%E6%80%A7%EF%BC%88transform-style%EF%BC%89" tabindex="-1">转换如何呈现属性（transform-style）</h4><ol><li><code>transform-style</code>属性规定如何在<code>3D</code>空间中呈现被嵌套的元素</li><li><code>transform-style: flat | preserve-3d</code>，<code>flat</code>表示子元素将不保留其<code>3D</code>位置，<code>preserve-3d</code>表示子元素保留其<code>3D</code>位置</li><li>默认值是<code>flat</code>，不保留3D位置</li><li>下面用一个不同于上面几个示例的示例来展示整个属性的效果，你可以在demo中修改几个属性来查看效果</li></ol><pre><code class="language-HTML">&lt;div class=&quot;stage&quot;&gt;&lt;div class=&quot;box&quot;&gt;&lt;div class=&quot;box-out&quot;&gt;&lt;div class=&quot;box-in&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</code></pre><pre><code class="language-CSS">.stage {width: 100%;height: 100%;perspective: 1600px;-webkit-perspective: 1600px;}.stage .box {width: 400px;height: 400px;margin: 100px 100px;background-color: #55ffff;transform: rotate3d(0, 1, 0, 65deg);-webkit-transform: rotate3d(0, 1, 0, 65deg);// 这里会展示3D效果transform-style: preserve-3d;-webkit-transform-style: preserve-3d;// 不展示3D效果// transform-style: flat;// -webkit-transform-style: flat;}.stage .box .box-out {width: 90%;height: 90%;background-color: #ffff00;transform: rotate3d(0.5, 1, 0, 85deg);-webkit-transform: rotate3d(0.5, 1, 0, 85deg);}.stage .box .box-out .box-in {width: 150%;height: 150%;background-color: #aaff7f;transform: rotate3d(0.1, 1, 0.5, 85deg);-webkit-transform: rotate3d(0.1, 1, 0.5, 85deg);}</code></pre><h4 id="%E8%83%8C%E9%9D%A2%E6%98%AF%E5%90%A6%E5%8F%AF%E8%A7%81%E5%B1%9E%E6%80%A7%EF%BC%88backface-visibility%EF%BC%89" tabindex="-1">背面是否可见属性（backface-visibility）</h4><ol><li><code>backface-visibility</code>属性定义当元素不面向屏幕时是否可见</li><li><code>backface-visibility: visible | hidden</code>，<code>visible</code>表示背面是可见的，<code>hidden</code>表示背面不可见</li><li>该属性主要用于不想展示被旋转元素的背面时使用，通常用于一些翻转动效，比如炉石的抽卡，就属于背面可见</li><li>下面我们可以通过翻拍效果示例来理解一下这个属性的作用</li></ol><pre><code class="language-HTML">&lt;div class=&quot;stage&quot;&gt;&lt;div class=&quot;box&quot;&gt;&lt;div class=&quot;box-prev&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box-next&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</code></pre><pre><code class="language-CSS">.stage {width: 100%;height: 100%;perspective: 1600px;-webkit-perspective: 1600px;}.box {width: 400px;height: 400px;margin: 100px 100px;position: relative;transition: 0.5s all;transform-style: preserve-3d;-webkit-transform-style: preserve-3d;}.box-prev, .box-next {width: 100%;height: 100%;position: absolute;transition: 0.5s all;backface-visibility: hidden;}.box-prev {background-color: #ffff00;z-index: 1;}.box-next {background-color: #aaff7f;transform: rotateY(180deg);z-index: 0;}.box:hover{transform: rotateY(180deg);}</code></pre><h3 id="%E5%81%9A%E4%B8%80%E4%BA%9B%E7%AE%80%E5%8D%95%E7%9A%84%E5%BE%AA%E7%8E%AF%E5%8A%A8%E6%95%88" tabindex="-1">做一些简单的循环动效</h3><p>学习了上述代码之后，我们可以做一些简单的循环动效了，这里我们用一个简单的翻书效果来练习，当然还有很多奇思妙想期待你自己去实践啦！</p><pre><code class="language-HTML">&lt;div class=&quot;stage&quot;&gt;&lt;div class=&quot;box&quot;&gt;&lt;div class=&quot;box-prev&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box-midd&quot;&gt;&lt;/div&gt;&lt;div class=&quot;box-next&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</code></pre><pre><code class="language-CSS">.stage {width: 100%;height: 100%;}.box {width: 400px;height: 400px;margin: 100px 100px;position: relative;transition: 0.5s all;transform-style: preserve-3d;}.box-prev,.box-midd,.box-next {width: 100%;height: 100%;position: absolute;transition: 3s all;}.box-prev {background-color: #ffff9b;z-index: 2;}.box-midd {background-color: #aaff7f;z-index: 1;}.box-next {display: flex;justify-content: space-between;z-index: 0;}.box-next span {width: 50%;height: 100%;}.box-next span:nth-child(1) {background-color: #ffff9b;}.box-next span:nth-child(2) {background-color: #aaff7f;}.box:hover .box-prev {transform: perspective(800px) rotateY(-180deg);transform-origin: perspective(800px) 50% 50% 0;}.box:hover .box-midd {transform: perspective(800px) rotateY(-180deg);transform-origin: perspective(800px) 50% 50% 0;}</code></pre><p><a href="https://segmentfault.com/a/1190000006093695" target="_blank">参考文档一 ———— 带你玩转css3的3D！</a><br /><a href="https://www.zhangxinxu.com/wordpress/2012/09/css3-3d-transform-perspective-animate-transition/" target="_blank">参考文档二 ———— 好吧，CSS3 3D transform变换，不过如此！</a><br /><a href="https://www.w3school.com.cn/cssref/pr_perspective.asp" target="_blank">参考文档三 ———— CSS perspective 属性</a><br /><a href="https://www.w3school.com.cn/cssref/pr_perspective-origin.asp" target="_blank">参考文档三 ———— CSS perspective-origin 属性</a><br /><a href="https://www.w3school.com.cn/cssref/pr_transform.asp" target="_blank">参考文档五 ———— CSS transform 属性</a><br /><a href="https://www.w3school.com.cn/cssref/pr_transform-origin.asp" target="_blank">参考文档六 ———— CSS transform-origin 属性</a><br /><a href="https://www.w3school.com.cn/cssref/pr_transform-style.asp" target="_blank">参考文档七 ———— CSS transform-style 属性</a><br /><a href="https://www.w3school.com.cn/cssref/pr_backface-visibility.asp" target="_blank">参考文档八 ———— CSS backface-visibility 属性</a><br /><a href="https://www.jb51.net/css/462412.html" target="_blank">参考文档九 ———— CSS3中设置3D变形的transform-style属性详解</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llHCLS/blob/master/CSS3/css3-blog/2021/2021-10/transform3d.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Vue自定义组件实现v-model指令]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/v-model-imitate" />
                <id>tag:https://fx67ll.xyz,2021-11-22:v-model-imitate</id>
                <published>2021-11-22T14:47:45+08:00</published>
                <updated>2025-07-02T11:27:03+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月25日</strong></em></p><p><em><strong>Tips: 本文所描述的Vue均默认是Vue2版本</strong></em></p><p>在我们初次接触<code>Vue</code>的时候，一定会了解到一个语法糖，那就是<code>v-model</code>指令，它带给我们的第一印象就是它可以实现双向绑定</p><p>那么，什么是双向绑定？通俗一点来说，就是当数据发生变化的时候，视图同时发生变化，这可以说是<code>Vue</code>的精髓之处了<br />（不过关于双向绑定，后续可以出一篇更为详尽的博文来深入模拟一下Vue的实现，这里重点还是探讨实现自定义组件的v-model指令）</p><p>所以，在我们深入使用<code>Vue</code>之后，编写一个自定义组件，如何手动实现一个<code>v-model</code>的指令呢，今天我们就来详细探讨一下</p><h3 id="v-model%E6%8C%87%E4%BB%A4%E7%9A%84%E5%8E%9F%E7%90%86%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F" tabindex="-1"><code>v-model</code>指令的原理是什么？</h3><ol><li><code>v-bind</code>绑定一个<code>value</code>属性</li><li><code>v-on</code>监听当前元素的<code>input</code>事件，当数据变化时，将值传递给<code>value</code>实时更新数据</li></ol><h3 id="v-model%E5%92%8Cv-bind%3Avalue%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB%EF%BC%9F" tabindex="-1"><code>v-model</code>和<code>v-bind:value</code>有什么区别？</h3><p>自定义组件中，必定会使用<code>v-bind</code>指令来实现组件之间值的传递，所以在我还是菜鸟的那段时间，我一直有个疑惑，<br />既然有的<code>v-bind</code>指令，为什么还需要在自定义的组件中实现<code>v-model</code>指令呢？在我实践了一番之后，我才明白，<br /><code>v-model</code>既能够实现值的传递，也能够实现页面数据的实时变化，而<code>v-bind</code>只是实现值的传递，如果需要实现实时变化的效果，<br />需要使用另外的方法修改变量的值，可以总结为下面两点</p><ol><li><code>v-model</code>实现视图和数据的双向绑定，一者变化另一者也会同时变化</li><li><code>v-bind</code>只会在初始化的时候将数据绑定到视图上，后续视图变化不会影响数据</li></ol><h3 id="%E6%92%B8%E4%B8%80%E4%B8%AAv-model" tabindex="-1">撸一个<code>v-model</code></h3><p>看到这里，相信你也理解了为什么我们会需要在自定义的组件中自定义一个v-model指令，下面我们通过一个简易的示例来撸一个<code>v-model</code>，<br />在此之前我们需要在一个空<code>Vue</code>项目中，定义一个<a href="#jumpId-dad" title="点击跳转示例代码">dad.vue</a>文件，以及<a href="#jumpId-child" title="点击跳转示例代码">child.vue</a>文件。<br />为了方便初学者学习，我将一个<a href="https://github.com/fx67ll/fx67llVue/tree/master/vue-practice/imitate-v-model" target="_blank">完整的简易示例项目</a>放到了github仓库中供大家下载学习，<br />大家如果喜欢可以为了点一颗Star，Thanks♪(･ω･)ﾉ！！！</p><p><span id="jumpId-dad"></span></p><h4 id="dad.vue" tabindex="-1">dad.vue</h4><pre><code class="language-Vue">&lt;template&gt;&lt;div&gt;&lt;child v-model=&#39;childName&#39;&gt;&lt;/child&gt;&lt;/div&gt;&lt;/template&gt;&lt;script&gt;import child from &#39;./child.vue&#39;;export default {name: &#39;dad&#39;,components: {child},data() {return {childName: &#39;我是儿子&#39;};},methods: {}};&lt;/script&gt;</code></pre><p><span id="jumpId-child"></span></p><h4 id="child.vue" tabindex="-1">child.vue</h4><pre><code class="language-Vue">&lt;template&gt;&lt;!-- vue中的自定义组件中，若父组件中用v-model的话，其实相当于v-bind:value=&#39;***&#39;并且v-on:input=&#39;***&#39; --&gt;&lt;!-- 因此子组件内部用props接收value值，用$emit触发input事件，默认传递value值和input事件是模拟v-model的默认规则 --&gt;&lt;!-- 基础知识提示：@是v-on监听事件的简写，:是v-bind绑定属性的简写 --&gt;&lt;div class=&quot;box-v-model&quot;&gt;&lt;input type=&quot;text&quot; class=&quot;input-v-model&quot; :value=&quot;value&quot; @input=&quot;inputChange&quot; /&gt;&lt;!-- 简写的方式 --&gt;&lt;!-- &lt;input type=&quot;text&quot; @input=&quot;value=$event.target.value&quot; :value=&quot;value&quot; /&gt; --&gt;&lt;div&gt;{{ value }}&lt;/div&gt;&lt;/div&gt;&lt;/template&gt;&lt;script&gt;export default {name: &quot;child&quot;,props: {value: {type: String}},methods: {// $emit 方法可以触发当前实例上的事件，这里触发的事input事件，附加参数都会传给监听器回调// input 事件在用户输入时触发，它是在元素值发生变化时立即触发inputChange(e) {this.$emit(&quot;input&quot;, e.target.value);}}}&lt;/script&gt;</code></pre><p><a href="http://www.qiutianaimeili.com/html/page/2019/03/bzwpdjp0jos.html" target="_blank">参考文档一 ———— vue自定义组件中的v-model</a><br /><a href="https://blog.csdn.net/a1059526327/article/details/108981613" target="_blank">参考文档二 ———— Vue中v-model和v-bind:value的区别以及手动实现v-model</a><br /><a href="https://cn.vuejs.org/v2/api/#vm-emit" target="_blank">参考文档三 ———— Vue官方文档关于$emit的说明</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llVue/blob/master/vue-blog/2021/2021-10/imitate-v-model.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[快速入门上手Markdown]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/markdown-quickstart" />
                <id>tag:https://fx67ll.xyz,2021-11-22:markdown-quickstart</id>
                <published>2021-11-22T14:43:42+08:00</published>
                <updated>2025-07-02T11:28:19+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月22日</strong></em></p><p>第一次接触<code>Markdown</code>是写代码初期看很多大佬的<code>github</code>，他们的项目一定会有一份文件叫<code>Readme.md</code>的文件<br />他们由一些简单美观的符号和汉字字母组成，编译之后成为一篇简单直观的文档<br />深入了解之后我才知道，原来还有这样一种神奇的标记语言，可以写出这么好看的文档</p><p>当然，在我们记录一些技术文档或者写技术博客的时候，也经常会头疼，如何写一篇技术文档，能够同时以美观的格式发布到多个博客平台呢？</p><p><code>Markdown</code>就可以解决我们快速书写和多个平台适配的痛点，他可以用一些简单的标记符，来格式化你的文档，并且保证不同平台统一的格式</p><p>当然，由于各个平台的<code>Markdown解析器</code>的配置不同，解析后的一些样式，比如字体粗细，超链接颜色，代码块颜色之类的，可能会稍有不同</p><p>可以说，<code>Markdown</code>就是为简单而生</p><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFmarkdown%EF%BC%9F" tabindex="-1">什么是Markdown？</h3><ol><li><code>Markdown</code>是一种轻量级标记语言，它允许人们使用易读易写的纯文本格式编写文档</li><li>他是由<code>John Gruber</code>在2004年发明创建</li><li>由<code>Markdown</code>编写的文档可以导出<code>HTML 、Word、图像、PDF、Epub</code>等多种格式的文档</li><li>Markdown<code>编写的文档后缀为</code>.md<code>或</code>.markdown`</li><li>注意!!!<code>Markdown</code>支持部分<code>Html</code>的语法，部分展示复杂的内容可以用<code>Html</code>代码来写</li></ol><h3 id="markdown%E7%BC%96%E8%BE%91%E5%99%A8%E7%9A%84%E9%80%89%E6%8B%A9" tabindex="-1">Markdown编辑器的选择</h3><p>因为我个人是从js开始接触代码世界，所以我选择的第一个编辑器是<a href="https://www.dcloud.io/" target="_blank">Hbuilder</a>（注意官网已经不提供更新版本下载），<br />不过目前它已经升级版本到<a href="https://www.dcloud.io/hbuilderx.html" target="_blank">HbuilderX</a>，这不是一个专门的<code>Markdown编辑器</code>，<br />但是他的内部集成了插件具有直接编写<code>Markdown</code>的功能，就我个人而言我觉得非常适合写<code>Markdown</code>文档，不仅不耽误写代码，<br />而且它的<code>Markdown文本标注</code>的颜色非常符合我的审美。当然萝卜青菜各有所爱，还有以下一些<code>Markdown编译器</code>大家都可以试试</p><ol><li>各大博客网站的编写都是支持Markdown语法的，所以可以直接在网站上写，如：github，csdn，开源中国，掘金，思否之类的平台</li><li>专门的Markdown编辑器，如：<blockquote><p><a href="http://25.io/mou/" title="点击跳转官网地址" target="_blank">Mou</a><br /><a href="http://markdownpad.com/" title="点击跳转官网地址" target="_blank">MarkdownPad</a><br /><a href="https://www.typora.io/" title="点击跳转官网地址" target="_blank">Typora</a><br /><a href="https://atom.io/" title="点击跳转官网地址" target="_blank">Atom</a><br /><a href="http://pad.haroopress.com/user.html" title="点击跳转官网地址" target="_blank">Haroopad</a><br /><a href="http://www.sublimetext.com/" title="点击跳转官网地址" target="_blank">SublimeText</a><br /><a href="https://www.zybuluo.com/mdeditor" title="点击跳转官网地址" target="_blank">Cmd Markdown</a><br /><a href="https://bywordapp.com/" title="点击跳转官网地址" target="_blank">Byword</a><br /><a href="https://cloose.github.io/CuteMarkEd/" title="点击跳转官网地址" target="_blank">CuteMarkEd</a><br /><a href="https://dillinger.io/" title="点击跳转官网地址" target="_blank">Dillinger</a><br /><a href="http://ww1.epiceditor.com/" title="点击跳转官网地址" target="_blank">EpicEditor</a><br /><a href="http://jbt.github.io/markdown-editor/" title="点击跳转官网地址" target="_blank">MarkdownEditor</a><br /><a href="http://code52.org/DownmarkerWPF/" title="点击跳转官网地址" target="_blank">MarkPad</a><br /><a href="http://marxi.co/" title="点击跳转官网地址" target="_blank">Marxico</a></p></blockquote></li><li>最后，大部分的编译器都是可以安装<code>Markdown编译器</code>插件的，所以基本上可以直接在写代码的同时用Markdown写写技术文章哦</li></ol><h3 id="%E6%88%91%E5%B8%B8%E7%94%A8%E7%9A%84%E4%B8%80%E4%BA%9B%E8%AF%AD%E6%B3%95" tabindex="-1">我常用的一些语法</h3><p>***注意!!!***这里是我自己平时写文档和技术博客常用的一些Markdown标记，标注为我自己写文档使用的位置，不是这个标记的官方定义</p><pre><code class="language-"># 大标题  ### 段落标题  #### 特殊tip（会加粗成为目录中的标题，慎用，我是因为橘黄色才用这个作tip）1. 有序列表  + 无序列表  &gt; 引用（需要垂直连续使用）*文字* 倾斜**文字** 加粗***文字*** 加粗倾斜[外部链接标题](外部链接地址 &quot;标题&quot;) 外部链接，标题就是类似脚注，鼠标悬浮后显示的文字  &#96;单行代码&#96; 行内代码块   &#96;&#96;&#96;*2 多行代码块（注意这里是两排&#96;&#96;&#96;，中间写代码即可）[锚链接标题](#锚链接id) +  &lt;span id=&quot;#锚链接id&quot;&gt;&lt;/span&gt;  组合使用可以完成锚链接，在当前文章内跳转到指定位置  </code></pre><h3 id="markdown%E8%AF%AD%E6%B3%95%E5%A4%A7%E5%85%A8" tabindex="-1">Markdown语法大全</h3><ol><li><a href="#jumpId-title" title="点击跳转文字指定位置">标题</a></li><li><a href="#jumpId-p" title="点击跳转文字指定位置">段落</a></li><li><a href="#jumpId-list" title="点击跳转文字指定位置">列表</a></li><li><a href="#jumpId-use" title="点击跳转文字指定位置">引用</a></li><li><a href="#jumpId-code" title="点击跳转文字指定位置">代码</a></li><li><a href="#jumpId-link" title="点击跳转文字指定位置">链接</a></li><li><a href="#jumpId-picture" title="点击跳转文字指定位置">图片</a></li><li><a href="#jumpId-table" title="点击跳转文字指定位置">表格</a></li><li><a href="#jumpId-other" title="点击跳转文字指定位置">其他</a></li></ol><p><span id="jumpId-title"></span></p><h4 id="%E6%A0%87%E9%A2%98" tabindex="-1">标题</h4><ol><li>主要是用来写整个文档的目录结构的，需要注意的是各级标题的字体大小对应<code>h1-h6</code></li><li>可以用下划线突出一下标题，不过我本人不喜欢这么标记，根据字符数量不同，标记线的长度不同</li></ol><pre><code class="language-"># 一级标题## 二级标题### 三级标题#### 四级标题##### 五级标题###### 六级标题---- 标记线</code></pre><p><span id="jumpId-p"></span></p><h4 id="%E6%AE%B5%E8%90%BD" tabindex="-1">段落</h4><ol><li>在文字的末尾添加两个空格或者下一行空一行，都可以实现<em><strong>换行</strong></em></li><li>使用<code>*</code>或者<code>_</code>组合来实现加粗或者倾斜</li></ol><pre><code class="language-">*斜体文本*_斜体文本_**粗体文本**__粗体文本__***粗斜体文本***___粗斜体文本___</code></pre><ol start="3"><li>使用下面这些符号都可以建立分割线，根据符号数量不同分割线长度不同</li></ol><pre><code class="language-">**** * ******- - -----------</code></pre><ol start="4"><li>在文字的前后加上<code>~</code>或者<code>~~</code>可以实现删除线</li></ol><pre><code class="language-">~需要标注删除线的文字~~~需要标注删除线的文字~~</code></pre><ol start="5"><li>让文字拥有脚注，就是鼠标悬浮之后显示的文字</li></ol><pre><code class="language-">[^需要注明的文字]: 悬浮后显示的文字</code></pre><p><span id="jumpId-list"></span></p><h4 id="%E5%88%97%E8%A1%A8" tabindex="-1">列表</h4><ol><li>有序列表，直接用<code>1. 2. 3.</code>按顺序标注下去即可，注意是数字加<code>.</code></li></ol><pre><code class="language-">1. 第一项2. 第二项3. 第三项</code></pre><ol start="2"><li>无序列表，使用<code>*</code>或者<code>+</code>或者<code>-</code>按顺序标注下去即可</li></ol><pre><code class="language-">* 第一项* 第二项* 第三项+ 第一项+ 第二项+ 第三项- 第一项- 第二项- 第三项</code></pre><ol start="3"><li>嵌套列表，列表嵌套只需在子列表中的选项前面添加四个空格即可。注意列表下也可以使用<code>&gt;</code>来引用相关内容，注意引用换行需要在末尾加两个空格哦</li></ol><pre><code class="language-">1. 第一项：    * 第一项嵌套的第一个元素      * 第一项嵌套的第二个元素  2. 第二项：    + 第二项嵌套的第一个元素      + 第二项嵌套的第二个元素  3. 第三项- 第三项嵌套的第一个元素  - 第三项嵌套的第二个元素  4. 引用内容&gt; 引用一（注意如果要换行就加两个空格，默认是不换行，会自动把下面引用内容拼接过来）&gt; 引用二</code></pre><p><span id="jumpId-use"></span></p><h4 id="%E5%BC%95%E7%94%A8" tabindex="-1">引用</h4><ol><li>在段落开头添加<code>&gt;</code>，然后和段落之间要空一个空格。默认不换行，换行需要在末尾添加两个空格。支持嵌套</li></ol><pre><code class="language-">&gt; 引用一  &gt; 引用二  &gt; 引用三  &gt; 最外层&gt; &gt; 第一层嵌套&gt; &gt; &gt; 第二层嵌套</code></pre><ol start="2"><li>区块中使用列表和列表中使用区块参考上一节末尾</li></ol><pre><code class="language-">&gt; 区块中使用列表&gt; 1. 第一项&gt; 2. 第二项&gt; + 第一项&gt; + 第二项&gt; + 第三项* 列表中使用区块* 第一项    &gt; 引用一      &gt; 引用二  * 第二项</code></pre><p><span id="jumpId-code"></span></p><h4 id="%E4%BB%A3%E7%A0%81" tabindex="-1">代码</h4><ol><li>如果是段落上的一个函数或片段的代码可以用反引号把它包起来（`）(貌似没法用两个反引号把一个反引号写成代码片段？HbuilderX亲测不支持)</li></ol><pre><code class="language-">&#96;code()&#96; 函数</code></pre><ol start="2"><li>代码区块可以使用<code>4个空格</code>或者一个制表符 <kbd>Tab</kbd>键）即可。也可以使用两个（```）包裹一段代码，这样更为醒目，<br />我经常使用，并且你可以指定代码的语言，也可以不指定</li></ol><pre><code class="language-">// 这里用括号包裹防止误认为代码块  (&#96;&#96;&#96;)javascript$(document).ready(function () {    alert(&#39;RUNOOB&#39;);});(&#96;&#96;&#96;)</code></pre><p><span id="jumpId-link"></span></p><h4 id="%E9%93%BE%E6%8E%A5" tabindex="-1">链接</h4><ol><li>普通链接，直接按下方格式书写，有完整格式和简介格式</li></ol><pre><code class="language-">// 完整格式，可以重命名，另外可选外加标题，标题可以不写，标题类似脚注，悬浮显示文字  [链接名称](链接地址 &quot;链接标题&quot;)// 简洁格式&lt;链接标题&gt;</code></pre><ol start="2"><li>高级链接，我们可以通过变量来设置一个链接，变量赋值在文档末尾进行</li></ol><pre><code class="language-">这个链接用 1 作为网址变量 [Google][1]这个链接用 abc123 作为网址变量 [Baidu][abc123]这个链接用 runoob 作为网址变量 [Runoob][runoob]然后在文档的结尾为变量赋值（网址）  [1]: http://www.google.com/  [abc123]: http://www.baidu.com/  [rb]: http://www.rb.com/</code></pre><p><span id="jumpId-picture"></span></p><h4 id="%E5%9B%BE%E7%89%87" tabindex="-1">图片</h4><ol><li>基本语法，直接按照下方格式书写</li></ol><pre><code class="language-">![alt 属性文本](图片地址或图片网址)![alt 属性文本](图片地址或图片网址 &quot;可选标题&quot;)</code></pre><ol start="2"><li>高级语法，像网址那样使用变量</li></ol><pre><code class="language-">这个链接用 a 作为网址变量 [RB][a].然后在文档的结尾为变量赋值（图片地址或图片网址）  [a]: 图片地址或图片网址</code></pre><ol start="3"><li>没有办法指定图片的高度与宽度，如果你需要的话，你可以使用普通的<code>&lt;img&gt;</code>标签</li></ol><pre><code class="language-">&lt;img src=&quot;http://static.rb.com/images/rb-logo.png&quot; width=&quot;50%&quot;&gt;</code></pre><p><span id="jumpId-table"></span></p><h4 id="%E8%A1%A8%E6%A0%BC" tabindex="-1">表格</h4><ol><li>制作表格使用<code>|</code>来分隔不同的单元格，使用<code>-</code>来分隔表头和其他行</li></ol><pre><code class="language-">|  表头   | 表头  ||  ----  | ----  || 单元格  | 单元格 || 单元格  | 单元格 |</code></pre><ol start="2"><li>我们可以设置表格的对齐方式<ul><li><code>-:</code> 设置内容和标题栏居右对齐</li><li><code>:-</code> 设置内容和标题栏居左对齐</li><li><code>:-:</code> 设置内容和标题栏居居中对齐</li></ul></li></ol><pre><code class="language-">| 左对齐 | 右对齐 | 居中对齐 || :-----| ----: | :----: || 单元格 | 单元格 | 单元格 || 单元格 | 单元格 | 单元格 |</code></pre><ol start="3"><li>复杂表格，如需要合并表头之类的，可以直接使用<code>Html</code>中提供的表格</li></ol><pre><code class="language-">&lt;table&gt;&lt;tr&gt;        &lt;th colspan=&quot;3&quot;&gt;js-pratice&lt;/th&gt;    &lt;/tr&gt;&lt;tr&gt;    &lt;th&gt;一级目录&lt;/th&gt;    &lt;th&gt;二级目录&lt;/th&gt;    &lt;th&gt;描述&lt;/th&gt;  &lt;/tr &gt;&lt;tr &gt;    &lt;td rowspan=&quot;3&quot;&gt;Array&lt;/td&gt;    &lt;td&gt;Basic&lt;/td&gt;    &lt;td&gt;数组基本操作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;    &lt;td&gt;Repeat&lt;/td&gt;    &lt;td&gt;数组去重操作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;    &lt;td&gt;Sort&lt;/td&gt;    &lt;td&gt;数组排序操作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;    &lt;td &gt;Date&lt;/td&gt;    &lt;td&gt;Date&lt;/td&gt;    &lt;td&gt;日期对象操作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;    &lt;td &gt;Inherit&lt;/td&gt;    &lt;td &gt;Inherit&lt;/td&gt;    &lt;td &gt;继承对象操作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;    &lt;td &gt;Promise&lt;/td&gt;    &lt;td &gt;Promise&lt;/td&gt;    &lt;td &gt;Promise对象操作&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</code></pre><p><span id="jumpId-other"></span></p><h4 id="%E5%85%B6%E4%BB%96" tabindex="-1">其他</h4><ol><li>支持<code>Html</code>代码<blockquote><p>例如：<code>Html</code>转义字符在行首添加空格来实现，<code>&amp;ensp;</code>代表半角空格，<code>&amp;emsp;</code>代表全角空格<br />例如：借助<code>&lt;br/&gt;</code>来在表格中实现换行</p></blockquote></li><li>用<code>\</code>包裹来转义</li><li>部分高级<code>Markdown</code>编辑器支持使用<code>$$</code>包裹来使用数学公式</li><li>部分高级<code>Markdown</code>编辑器支持流程图的编译和展示</li></ol><p><a href="https://www.runoob.com/markdown/md-tutorial.html" title="点击跳转网页地址" target="_blank">参考文档一 ———— 菜鸟教程 Markdown 教程</a><br /><a href="https://blog.csdn.net/davidhzq/article/details/100815332" title="点击跳转网页地址" target="_blank">参考文档二 ———— 几款主流好用的markdown编辑器介绍</a><br /><a href="https://mazhuang.org/2017/09/01/markdown-odd-skills/#%E8%A1%8C%E9%A6%96%E7%BC%A9%E8%BF%9B" target="_blank">参考文档三 ———— 关于 Markdown 的一些奇技淫巧</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJs/blob/master/js-blog/2021/2021-10/markdown.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[微信登录前端开发指南]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/wxlogin-develop-guide" />
                <id>tag:https://fx67ll.xyz,2021-11-22:wxlogin-develop-guide</id>
                <published>2021-11-22T14:40:28+08:00</published>
                <updated>2025-07-02T11:34:32+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月21日</strong></em></p><h3 id="%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF" tabindex="-1">使用场景</h3><ol><li>微信公众号内嵌H5网页调用微信登录</li><li>在微信浏览器中的网页唤起微信登录界面</li></ol><h4 id="%E8%AF%A6%E6%83%85%E5%8F%AF%E4%BB%A5%E6%9F%A5%E9%98%85%E5%BE%AE%E4%BF%A1%E7%99%BB%E5%BD%95%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3-%E9%93%BE%E6%8E%A5%E5%9C%B0%E5%9D%80" tabindex="-1">详情可以查阅微信登录官方文档 <a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html" target="_blank">链接地址</a></h4><h3 id="%E5%8A%9F%E8%83%BD%E6%80%9D%E8%B7%AF" tabindex="-1">功能思路</h3><ol><li>后台先在微信给开发者提供的测试账号平台上创建应用，并把前台开发同学的微信添加到这个平台中，用于提供测试公众号，<a href="https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&amp;t=sandbox/index" target="_blank">平台地址</a></li><li>前台拼装各种微信登录必须的参数，并将页面跳转到微信指定的连接获取微信登录<code>code</code>参数</li><li>前台解析返回的链接，获取链接中的参数，并将<code>code</code>参数传给后台</li><li>由于后续获取的参数安全等级较高，所以后续的操作均由后台完成，并将数据存储在服务端<ul><li>后台通过<code>code</code>参数换取<code>access_token</code>参数和<code>openid</code>参数并存储在服务端</li><li>后台再通过<code>access_token</code>参数和<code>openid</code>参数换取用户详细信息，并将这两步包装成接口，让前台调用，返回前端登录状态完成微信登录操作</li></ul></li><li>如果<code>access_token</code>参数失效，使用<code>refresh_token</code>刷新即可，详细操作查阅文档，一般用不到这步</li></ol><h3 id="%E5%89%8D%E7%AB%AF%E4%BB%A3%E7%A0%81" tabindex="-1">前端代码</h3><h4 id="1.-%E8%8E%B7%E5%8F%96code" tabindex="-1">1. 获取code</h4><pre><code class="language-JavaScript">function getWXcode() {// 公众号的唯一标识，找公众号管理员提供var APPID = &#39;公众号的appid&#39;;// 授权后重定向的回调链接地址，要使用encodeURIComponent()对其进行编码处理// !!!注意回调的域名必须先让公众号管理员添加到安全域名中，否则无法完成跳转var REDIRECT_URI = encodeURIComponent(&#39;授权后跳转回的页面&#39;);// 常量，返回类型，填写code即可var RESPONSE_TYPE = &#39;code&#39;;// 常量，应用授权作用域// snsapi_userinfo （弹出授权页面，可通过openid拿到昵称、性别、所在地。并且， 即使在未关注的情况下，只要用户授权，也能获取其信息 ）// snsapi_base （不弹出授权页面，直接跳转，只能获取用户openid）var SCOPE = &#39;snsapi_userinfo&#39; || &#39;snsapi_base&#39;;// 重定向后会带上state参数，开发者可以填写a-zA-Z0-9的参数值，最多128字节// 除了此项是非必须参数，其他都是必须要带的参数// 注意！！！该参数通常用于判断跳转回来之前的接口是什么作用，比如用于判断是第一次微信登录还是授权过的快速微信登录var STATE = &#39;可以自定义的返回参数&#39;;// 无论直接打开还是做页面302重定向时候，必须带&#39;#wechat_redirect&#39;参数// 由于授权操作安全等级较高，所以在发起授权请求时，微信会对授权链接做正则强匹配校验，如果链接的参数顺序不对，授权页面将无法正常访问location.href =&#96;https://open.weixin.qq.com/connect/oauth2/authorize?appid=${APPID}&amp;redirect_uri=${REDIRECT_URI}&amp;response_type=${RESPONSE_TYPE}&amp;scope=${SCOPE}&amp;state=STATE#wechat_redirect &#96;;}</code></pre><h4 id="2.-%E8%A7%A3%E6%9E%90%E5%8F%82%E6%95%B0" tabindex="-1">2. 解析参数</h4><pre><code class="language-JavaScript">function parseData() {// 截取地址栏?后面的内容var search = location.search.slice(1);// 用&amp;标识分割成数组var arr = search.split(&#39;&amp;&#39;);// 处理数组转换成对象，并返回var result = {};arr.forEach(function(item) {var itemArr = item.split(&#39;=&#39;);result[itemArr[0]] = itemArr[1];});}</code></pre><h4 id="3.-code%E4%BC%A0%E7%BB%99%E5%90%8E%E5%8F%B0%EF%BC%8C%E5%90%8E%E5%8F%B0%E5%AE%8C%E6%88%90%E5%BE%AE%E4%BF%A1%E7%99%BB%E5%BD%95%E5%90%8E%E7%BB%AD%E6%93%8D%E4%BD%9C%E5%90%8E%EF%BC%8C%E8%BF%94%E5%9B%9E%E7%99%BB%E5%BD%95%E7%8A%B6%E6%80%81%E6%8F%90%E7%A4%BA%E5%89%8D%E7%AB%AF%E7%99%BB%E5%BD%95%E6%88%90%E5%8A%9F%E8%BF%98%E6%98%AF%E5%A4%B1%E8%B4%A5%EF%BC%8C%E7%84%B6%E5%90%8E%E5%89%8D%E5%8F%B0%E7%BB%A7%E7%BB%AD%E5%90%8E%E7%BB%AD%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99" tabindex="-1">3. code传给后台，后台完成微信登录后续操作后，返回登录状态提示前端登录成功还是失败，然后前台继续后续业务逻辑代码编写</h4><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llDoc/blob/main/wx-login/wx-login.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[微信支付前端开发指南]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/wxpay-develop-guide" />
                <id>tag:https://fx67ll.xyz,2021-11-22:wxpay-develop-guide</id>
                <published>2021-11-22T14:37:59+08:00</published>
                <updated>2025-07-02T11:26:59+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月21日</strong></em></p><h3 id="%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF" tabindex="-1">使用场景</h3><ol><li>微信公众号内嵌H5网页调用微信支付</li><li>在微信浏览器中的网页唤起微信支付界面</li></ol><h4 id="%E8%AF%A6%E6%83%85%E5%8F%AF%E4%BB%A5%E6%9F%A5%E9%98%85%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3-%E9%93%BE%E6%8E%A5%E5%9C%B0%E5%9D%80" tabindex="-1">详情可以查阅微信支付官方文档 <a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&amp;index=6" target="_blank">链接地址</a></h4><h3 id="%E5%8A%9F%E8%83%BD%E6%80%9D%E8%B7%AF" tabindex="-1">功能思路</h3><ol><li>后台整合微信官方的相关下单接口，编写预下单接口提供前台调用，并返回订单相关参数</li><li>前台按照下方示例代码，传递相关订单参数后唤起微信支付界面</li><li>后台整合微信官方订单状态查询接口，编写订单状态查询接口提供前台调用，并返回订单支付状态</li><li>用户支付完后，通过微信返回的参数以及步骤3的订单状态接口来判断订单是否支付成功，完成后续业务逻辑操作</li></ol><h3 id="%E5%89%8D%E7%AB%AF%E4%BB%A3%E7%A0%81" tabindex="-1">前端代码</h3><h4 id="1.-%E8%AE%BE%E7%BD%AE%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E8%B0%83%E7%94%A8%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98%E7%9A%84%E5%8F%98%E9%87%8F" tabindex="-1">1. 设置判断是否可以调用微信支付的变量</h4><pre><code class="language-JavaScript">let can_pay;</code></pre><h4 id="2.-%E5%88%A4%E6%96%ADweixinjsbridge%E5%AF%B9%E8%B1%A1%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8%E5%BD%93%E5%89%8D%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%AD%EF%BC%8C%E8%AF%A5%E5%AF%B9%E8%B1%A1%E5%8F%AA%E5%9C%A8%E5%BE%AE%E4%BF%A1%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%AD%E6%9C%89%E6%95%88%EF%BC%8C%E5%B9%B6%E5%9C%A8%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0%E4%B8%AD%E9%80%9A%E8%BF%87%E8%AE%BE%E7%BD%AE%E5%88%A4%E6%96%AD%E5%8F%98%E9%87%8F%E6%9D%A5%E7%A1%AE%E8%AE%A4%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98%E5%8F%AF%E7%94%A8%EF%BC%8C%E8%B0%83%E7%94%A8%E5%BE%AE%E4%BF%A1%E7%9B%B8%E5%85%B3api" tabindex="-1">2. 判断WeixinJSBridge对象是否存在当前浏览器中，该对象只在微信浏览器中有效，并在回调函数中通过设置判断变量来确认微信支付可用，调用微信相关API</h4><pre><code class="language-JavaScript">if (typeof WeixinJSBridge === &#39;undefined&#39;) {if (document.addEventListener) {document.addEventListener(&#39;WeixinJSBridgeReady&#39;, onBridgeReady, false);} else if (document.attachEvent) {document.attachEvent(&#39;WeixinJSBridgeReady&#39;, onBridgeReady);document.attachEvent(&#39;onWeixinJSBridgeReady&#39;, onBridgeReady);}} else {onBridgeReady();}onBridgeReady() {can_pay = true;WeixinJSBridge.call(&#39;hideOptionMenu&#39;); // 微信API，用于隐藏右上角按钮，与之对应的是显示右上角按钮（showOptionMenu），还有显示隐藏底部导航栏的API（showToolbar/hideToolbar）}</code></pre><h4 id="3.-%E5%A6%82%E6%9E%9C%E5%88%A4%E6%96%AD%E5%8F%98%E9%87%8F%E4%B8%BA%E7%9C%9F%E5%88%99%E5%BC%80%E5%A7%8B%E9%80%9A%E8%BF%87%E5%AF%B9%E8%B1%A1%E5%94%A4%E8%B5%B7%E6%94%AF%E4%BB%98%E7%95%8C%E9%9D%A2%EF%BC%8C%E5%B9%B6%E5%9C%A8%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0%E4%B8%AD%E5%AE%8C%E6%88%90%E5%85%B6%E4%BB%96%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E6%93%8D%E4%BD%9C%EF%BC%8C%E6%B3%A8%E6%84%8F%E6%9F%A5%E7%9C%8B%E6%B3%A8%E9%87%8A%E5%86%85%E5%AE%B9%E4%B8%8D%E8%A6%81%E7%9B%B4%E6%8E%A5%E5%A4%8D%E5%88%B6%E5%85%A8%E9%83%A8%E4%BB%A3%E7%A0%81%E4%B8%8D%E5%81%9A%E4%BF%AE%E6%94%B9" tabindex="-1">3. 如果判断变量为真则开始通过对象唤起支付界面，并在回调函数中完成其他业务逻辑操作，注意查看注释内容不要直接复制全部代码不做修改</h4><pre><code class="language-JavaScript">if (can_pay) {WeixinJSBridge.invoke(&#39;getBrandWCPayRequest&#39;,{// res.data对象为后台返回参数对象appId: res.data.appId, // 公众号id，为当前服务商号绑定的appidtimeStamp: res.data.timeStamp, // 时间戳，自1970年以来的秒数nonceStr: res.data.nonceStr, // 随机字符串，不长于32位package: res.data.payInfo, // 订单详情扩展字符串，统一下单接口返回的prepay_id参数值signType: res.data.signType, // 签名方式，默认为MD5，支持HMAC-SHA256和MD5paySign: res.data.sign // 签名},// 微信支付官方接口的返回函数function(res) {if (res.err_msg === &#39;get_brand_wcpay_request:ok&#39;) {console.log(&#39;这里调用后台的订单状态查询函数，如果后台查询成功则微信支付成功，反之失败&#39;)} else {console.log(&#39;支付异常&#39;)}});}</code></pre><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llDoc/blob/main/wx-pay/wx-pay.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[快速入门上手JavaScript中的Promise]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/promise-quickstart" />
                <id>tag:https://fx67ll.xyz,2021-11-22:promise-quickstart</id>
                <published>2021-11-22T14:21:29+08:00</published>
                <updated>2025-07-02T11:26:49+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月20日</strong></em></p><p>当我还是一个小白的时候，我翻了很多关于<code>Promise</code>介绍的文档，我一直没能理解所谓解决异步操作的痛点是什么意思</p><p>直到我翻了谷歌第一页的所有中文文档我才有所顿悟，其实从他的英文字面意思理解最为简单粗暴</p><p>这就是一个承诺，相当于在代码中提供一个在任何时候承诺之后要做什么事的方式，这个承诺可能会兑现也可能无法兑现，当然也可能在兑现的过程中</p><p>用这个来替代我们曾经需要写的回调函数，可以避免JavaScript程序中的回调地狱</p><p>所以先不去学习的语法，从另一个方式先理解，希望可以帮助你更好的学习或上手<code>Promise</code></p><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFpromise%3F" tabindex="-1">什么是Promise?</h3><p><code>Promise</code>是异步编程的一种解决方案，可以替代传统的解决方案，比如回调函数和事件<br />其在ES6中统一了用法，并提供了原生的<code>Promise对象</code>，<code>Promise对象</code>表示异步操作的最终完成(或失败)及其结果值<br />作为对象，<code>Promise</code>有以下两个特点：</p><ol><li>对象的状态不受外界影响<ul><li>只有异步操作的结果，可以决定当前是哪一种状态，任何其他操作都无法改变这个状态，<br />这也是<code>Promise</code>这个名字的由来，它的英语意思就是承诺，表示其他手段无法改变</li></ul></li><li>一旦状态改变了就不会在变，也就是说任何时候Promise都只有一种状态<ul><li><code>pending</code>：初始状态，不是成功或失败状态；<br /><code>fulfilled(resolved)</code>：操作成功完成状态；<br /><code>rejected</code>：操作失败状态</li><li><code>Promise对象</code>的状态改变，只有两种可能：从<code>Pending</code>变为<code>Resolved</code>和从<code>Pending</code>变为<code>Rejected</code>，<br />只要这两种情况发生，状态就凝固了，不会再变了，会一直保持这个结果，<br />就算改变已经发生了，你再对<code>Promise对象</code>添加回调函数，也会立即得到这个结果，<br />这与事件（Event）完全不同，事件的特点是，如果你错过了它，再去监听，是得不到结果的</li></ul></li></ol><h3 id="promise%E7%9A%84%E5%88%9B%E5%BB%BA%E5%92%8C%E7%94%A8%E9%80%94" tabindex="-1">Promise的创建和用途</h3><ol><li>构造函数接受一个名为<code>executor</code>的函数，此执行函数接受两个<code>f函数</code>参数，<code>resolve</code>和<code>reject</code>。</li></ol><pre><code class="language-JavaScript">new Promise( /* executor */ function(resolve, reject { ... }) );</code></pre><ol start="2"><li>Promise通常用于阻塞代码和异步操作，其中包括文件调用，API调用，DB调用，IO调用等等</li><li>这些异步操作的启动发生在执行函数中，如果异步操作成功，则通过<code>promise</code>的创建者调用<code>resolve()</code>函数返回预期结果，<br />同样，如果出现意外错误，则通过调用<code>reject()</code>函数传递错误具体信息</li><li>由于<code>promise</code>会立即执行，我们无法检查<code>promise</code>的初始状态，所以创建一个需要时间执行的<code>promise</code>最简单的方式就是<br />使用<code>setTimeOut()</code>函数，通过浏览器控制台的输出可以直观看到之前所说的状态，以及他的变化过程</li></ol><pre><code class="language-JavaScript">var promiseFir = new Promise(function(resolve, reject) {setTimeout(function() {resolve({message: &#39;与大家分享知识很开心！&#39;,code: 200});}, 2000)})console.log(promiseFir);setTimeout(function() {console.log(promiseFir);}, 2000)</code></pre><h3 id="promise%E7%9A%84%E6%96%B9%E6%B3%95" tabindex="-1">Promise的方法</h3><ol><li>3种原型方法<ul><li><a href="#jumpId-then">Promise.prototype.then(onFulfilled, onRejected) 链式操作</a></li><li><a href="#jumpId-catch">Promise.prototype.catch(onRejected) 捕捉错误</a></li><li><a href="#jumpId-finally">Promise.prototype.finally(onFinally) 最终操作</a></li><li>下面用一个小示例简单快速了解下这三个原型方法的用法</li></ul></li></ol><pre><code class="language-JavaScript">// 用一个小故事举例，你是一个上学的孩子，你问你的妈妈要一个电话。她说: 这个月底我要买一部手机var momsPromise = new Promise(function(resolve, reject) {momsSavings = 20000;  // 因为妈妈没有足够储蓄所以无法买到礼物  // momsSavings = 200000;  // 如果妈妈有足够的储蓄就可以买到礼物priceOfPhone = 60000;if (momsSavings &gt; priceOfPhone) {resolve({brand: &quot;iphone&quot;,model: &quot;6s&quot;});} else {reject(&quot;我们没有足够的储蓄，让我们多存点钱吧。&quot;);}});momsPromise.then(function(value) {console.log(&quot;哇，我得到这个电话作为礼物 &quot;, JSON.stringify(value));});momsPromise.catch(function(reason) {console.log(&quot;妈妈不能给我买电话，因为 &quot;, reason);});momsPromise.finally(function() {console.log(&quot;不管妈妈能不能给我买个电话，我仍然爱她&quot;);});</code></pre><ol start="2"><li>4种静态方法<ul><li><a href="#jumpId-resolve-reject">Promise.resolve()</a></li><li><a href="#jumpId-resolve-reject">Promise.reject()</a></li><li><a href="#jumpId-all">Promise.all()</a></li><li><a href="#jumpId-race">Promise.race()</a></li></ul></li></ol><p><span id="jumpId-then"></span></p><h4 id="promise.prototype.then(onfulfilled%2C-onrejected)-%E9%93%BE%E5%BC%8F%E6%93%8D%E4%BD%9C" tabindex="-1">Promise.prototype.then(onFulfilled, onRejected) 链式操作</h4><ol><li><code>Promise.prototype.then()</code>方法返回的是一个新的<code>Promise对象</code>，因此可以采用链式写法</li><li><code>Promise.prototype.then()</code>方法带有以下三个参数：成功回调，失败回调，前进回调，一般情况下只需要实现第一个，后面是可选的</li></ol><pre><code class="language-JavaScript">function ptFir() {return new Promise(function(resolve, reject) {setTimeout(function() {console.log(&#39;方法1执行&#39;);resolve(&#39;方法1执行完毕&#39;);}, 2000)})}function ptSec() {return new Promise(function(resolve, reject) {setTimeout(function() {console.log(&#39;方法2执行&#39;);resolve(&#39;方法2执行完毕&#39;);}, 1000)})}function ptThi() {return new Promise(function(resolve, reject) {setTimeout(function() {console.log(&#39;方法3执行&#39;);resolve(&#39;方法3执行完毕&#39;);}, 3000)})}ptFir().then(function(data) {console.log(&#39;第一个回调：&#39; + data);return ptSec();}).then(function(data) {console.log(&#39;第二个回调：&#39; + data);return ptThi();}).then(function(data) {console.log(&#39;第三个回调：&#39; + data);return &#39;还没完？该结束了吧！&#39;;}).then(function(data) {console.log(data);})</code></pre><p><span id="jumpId-catch"></span></p><h4 id="promise.prototype.catch(onrejected)-%E6%8D%95%E6%8D%89%E9%94%99%E8%AF%AF" tabindex="-1">Promise.prototype.catch(onRejected) 捕捉错误</h4><ol><li><code>Promise.prototype.catch()</code>方法是<code>Promise.prototype.then(null, rejection)</code>的别名，用于指定发生错误时的回调函数</li><li><code>Promise对象</code>的错误具有**“冒泡”**性质，会一直向后传递，直到被捕获为止，也就是说，错误总是会被下一个<code>catch</code>语句捕获</li></ol><pre><code class="language-JavaScript">promise().then(function(data) {// todo something}).catch(function(err) {// 处理上个回调函数的错误  })</code></pre><p><span id="jumpId-finally"></span></p><h4 id="promise.prototype.finally(onfinally)-%E6%9C%80%E7%BB%88%E6%93%8D%E4%BD%9C" tabindex="-1">Promise.prototype.finally(onFinally) 最终操作</h4><p>不管<code>promise对象</code>最后的状态，在执行完<code>.then</code>或<code>catch</code>指定的回调函数以后，都会执行<code>finally</code>方法指定的回调函数</p><pre><code class="language-JavaScript">promise().then(function(data) {// todo something}).catch(function(err) {// 处理上个回调函数的错误  }).finally(function(result) {// 处理then/catch之后必须执行的操作})</code></pre><p><span id="jumpId-resolve-reject"></span></p><h4 id="promise.resolve()-%2F-promise.reject()" tabindex="-1">Promise.resolve() / Promise.reject()</h4><ol><li>这两个是帮助方法或快捷方式，它们可以帮助您轻松创建<code>resolve</code>和<code>reject</code>方法</li><li>需要注意的是：如果<code>Promise.resolve()</code>方法的参数，<br />不是具有<code>.then()</code>方法的对象（又称<code>thenable对象</code>），则返回一个新的<code>Promise对象</code>，且它的状态为<code>fulfilled</code>；<br />如果<code>Promise.resolve</code>方法的参数是一个<code>Promise对象</code>的实例，则会被原封不动地返回</li><li><code>Promise.reject()</code>方法同上</li></ol><pre><code class="language-JavaScript">var promise = Promise.resolve(&#39;Hello，这里是执行成功的内容！&#39;);// var promise = Promise.reject(&#39;出错了，这里是被发现的错误！&#39;);  // 成功和失败只会有一种状态哦  promise.then(function(data) {console.log(data)});promise.catch(function(err) {console.log(err)});</code></pre><p><span id="jumpId-all"></span></p><h4 id="promise.all()" tabindex="-1">Promise.all()</h4><ol><li>当你处理多个<code>promise</code>时，最好先创建一个<code>promise数组</code>，然后对这些<code>promise集</code>执行必要的操作</li><li><code>Promise.all(iterable) 方法</code>返回一个<code>Promise实例</code>，此实例在<code>iterable参数</code>内所有的<code>promise</code></li><li>都完成（resolved）或参数中不包含<code>promise</code>时回调完成（resolve）</li><li>如果参数中<code>promise</code>有一个失败（rejected），此实例回调失败（reject），失败原因的是第一个<code>失败promise</code>的结果</li><li>***注意!!!***这里的异步操作是并行执行的，等到它们都执行完后才会进到<code>then()</code>里面，<br />并且<code>all()</code>方法会把所有异步操作的结果放进一个数组中传给<code>then()</code></li><li><em><strong>下面的示例需要说明的内容</strong></em><ul><li>只有3个方法的状态都变成<code>fulfilled</code>，p的状态才会变成<code>fulfilled</code>，此时3个方法的返回值组成一个数组，传递给p的回调函数</li><li>只要3个方法之中有一个被<code>rejected</code>，p的状态就变成<code>rejected</code>，此时第一个被<code>reject</code>的实例的返回值，会传递给p的回调函数</li></ul></li></ol><pre><code class="language-JavaScript">function ptFir() {return new Promise(function(resolve, reject) {setTimeout(function() {console.log(&#39;方法1执行&#39;);resolve(&#39;方法1执行完毕&#39;);}, 2000)})}function ptSec() {return new Promise(function(resolve, reject) {setTimeout(function() {console.log(&#39;方法2执行&#39;);resolve(&#39;方法2执行完毕&#39;);}, 1000)})}function ptThi() {return new Promise(function(resolve, reject) {setTimeout(function() {console.log(&#39;方法3执行&#39;);resolve(&#39;方法3执行完毕&#39;);}, 3000)})}Promise.all([ptFir(), ptSec(), ptThi()]).then(function(data) {console.log(data);console.log({}.toString.call(data));})</code></pre><p><span id="jumpId-race"></span></p><h4 id="promise.race()" tabindex="-1">Promise.race()</h4><ol><li><code>Promise.race(iterable)</code>方法返回一个<code>promise</code>，一旦迭代器中的某个<code>promise</code>解决或拒绝，返回的<code>promise</code>就会解决或拒绝</li><li><code>all()</code>方法的效果实际上是**「谁跑的慢，以谁为准执行回调」** ，<br />那么相对的就有另一个方法**「谁跑的快，以谁为准执行回调」**，这就是<code>race()</code>方法，这个词本来就是赛跑的意思</li><li>这个<code>race()</code>方法有什么用呢？使用场景还是很多的，比如我们可以用<code>race()</code>给某个异步请求设置超时时间，<br />并且在超时后执行相应的操作</li></ol><pre><code class="language-JavaScript">// ptFir(), ptSec(), ptThi() 同上Promise.all([ptFir(), ptSec(), ptThi()]).then(function(data) {console.log(data);console.log({}.toString.call(data));})</code></pre><h3 id="%E4%BD%BF%E7%94%A8promise%E7%9A%84%E4%B8%80%E4%BA%9B%E7%BB%8F%E9%AA%8C%E6%B3%95%E5%88%99" tabindex="-1">使用Promise的一些经验法则</h3><ol><li>进行异步操作或使用阻塞代码时，请使用<code>Promise</code></li><li>为了代码的可读性，<code>resolve()</code>方法对待<code>.then()</code>, <code>reject()</code>方法对应<code>.catch()</code></li><li>确保同时写入<code>.catch()</code>和<code>.then()</code>方法来实现所有的<code>promise</code></li><li>如果在这两种情况下都需要做一些事情，请使用<code>.finally()</code></li><li>我们只有一次改变每个<code>promise (单一原则)</code></li><li>我们可以在一个<code>promise</code>中添加多个处理程序</li><li><code>Promise对象</code>中所有方法的返回类型，无论是静态方法还是原型方法，都是<code>Promise</code></li><li>在<code>all()</code>方法中，无论哪个<code>promise</code>首先未完成，<code>promise</code>的顺序都保持在值变量中</li></ol><p><a href="https://blog.csdn.net/weixin_34273479/article/details/86352877" target="_blank">参考文档一  ———— 彻底理解Javascript 中的 Promise</a><br /><a href="https://zhuanlan.zhihu.com/p/98383032" target="_blank">参考文档二  ———— 通俗浅显的理解Promise中的then</a><br /><a href="https://segmentfault.com/a/1190000017312249" target="_blank">参考文档三  ———— 理解 Javascript 中的 Promise</a><br /><a href="https://www.runoob.com/w3cnote/javascript-promise-object.html" target="_blank">参考文档四  ———— JavaScript Promise 对象</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJs/blob/master/js-blog/2021/2021-10/promise.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[浅谈Java和JavaScript中变量和数据类型的区别]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/java-javascript-variable" />
                <id>tag:https://fx67ll.xyz,2021-11-22:java-javascript-variable</id>
                <published>2021-11-22T14:10:16+08:00</published>
                <updated>2025-07-02T20:25:07+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月19日</strong></em></p><p>对于一门编程语言的学习，如果第一步是安装环境，那么第二步一定是学习这门语言的基本规则，变量和数据类型则首当其冲<br />JavaScipt作为一个蹭Java热度而命名的语言，在很多方面和Java也有一定的相似之处<br />所以在我决定整理复习Java知识的时候，我希望通过一篇对比的文章，来顺便复习一下JavaScipt</p><p>当然还有一个理由就是，纯写Java或者纯写JavaScipt的文章，很多人都做过了，再写着实没什么意思 😃</p><h3 id="%E8%AE%BE%E8%AE%A1%E5%8C%BA%E5%88%AB" tabindex="-1">设计区别</h3><ol><li>Java是一种完全面向对象的编程语言，想要运用Java写东西就必须先设计对象；<br />而JavaScript不一样，它是基于对象的一种脚本语言，它为开发者自带提供了很多内部对象，要更为简易省时</li><li>Java采用静态联编，Java的对象引用必须在编译时就进行，编译器需要实现强类型检查；<br />而JavaScript不同，它采用动态联编，它的对象引用可以在运行时检查</li><li>它们最本质的不同就是用途，Java目前被广泛应用于PC端、手机端、互联网、数据中心等等；<br />而JavaScript则被主要用于嵌入文本到HTML页面，读写HTML元素，控制cookies等</li><li><a href="https://www.cnblogs.com/chinasitu/p/7245203.html" target="_blank">设计区别中的内容参考了这篇博文 —— Java跟JavaScript两者间的关系和区别</a></li></ol><h3 id="%E5%85%B3%E4%BA%8E%E5%8F%98%E9%87%8F" tabindex="-1">关于变量</h3><ol><li>不同之处<ul><li>在Java中，因为其强类型语言的特性，声明变量需要明确该变量的类型，内存管理系统根据变量的类型为变量分配存储空间</li><li>在JavaScript中，声明变量不需要指定变量类型，只需使用<code>var</code>关键字；<br />不过在ES6的后续计划中，添加了<code>let</code>和<code>const</code>两个关键字用于声明不同作用的变量，但是依旧不需要声明变量类型</li><li>而且JavaScipt中的变量可以存储其他类型的变量的值，但是在Java中则有明确的类型要求，不过部分可以通过类型转换来存储其他类型的值</li><li>值得一提的是，虽然JavaScript不要求声明变量类型，但是可以通过<code>new</code>关键字来声明其类型</li></ul></li><li>相同之处<ul><li>变量是用于存储信息的<em><strong>容器</strong></em>，所以他们都是需要申请内存来存储值，通过变量名可以引用值，也可以修改变量名的值</li><li>都是等号右边赋值给等号左边 😃</li><li>命名规则基本一致，具体参考下面的文章 <a href="#jumpId-NameType">Java和JavaScript命名规则</a></li><li><code>++a</code>代表取<code>++</code>运算之后的值，<code>a++</code>表示取<code>++</code>运算之前的值</li></ul></li></ol><p><span id="jumpId-NameType"></span></p><h4 id="java%E5%92%8Cjavascript%E5%91%BD%E5%90%8D%E8%A7%84%E5%88%99" tabindex="-1">Java和JavaScript命名规则</h4><ol><li>变量命名必须以字母、下划线<code>_</code>或者<code>$</code>为开头，其他字符可以是字母、<code>_</code>、<code>$</code>或数字</li><li>变量名中不允许使用空格和其他标点符号，首个字不能为数字，参考第一条</li><li>变量名长度不能超过255个字符<em><strong>注意!!!这是JavaScript的规则，Java参考各个类型的大小范围</strong></em></li><li>变量名区分大小写，建议驼峰命名法</li><li>变量名必须放在同一行中</li><li>不能使用关键字，分别具体是哪些自行百度</li><li>参考博文<ul><li><a href="https://www.cnblogs.com/chinasitu/p/7245203.html" target="_blank">命名规则中的内容参考了这篇博文 —— js变量命名规则</a></li><li><a href="https://blog.csdn.net/haopeng7816/article/details/85036928" target="_blank">命名规则中的内容参考了这篇博文 —— Java变量命名规则</a></li></ul></li></ol><h3 id="%E5%85%B3%E4%BA%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B" tabindex="-1">关于数据类型</h3><ol><li>不同之处<ul><li>提供的数据类型不同，具体可以参考下面 <a href="#jumpId-JavaType">Java数据类型</a> &amp; <a href="#jumpId-JsType">JavaScript数据类型</a> 两篇文章</li><li>类型转换并不相同，具体可以参考下面 <a href="#jumpId-JavaTypeChange">Java中的类型转换规则</a> &amp; <a href="#jumpId-JsTypeChange">JavaScript中的类型转换规则</a> 两篇文章</li></ul></li><li>相同之处<ul><li>在Java和JavaScript中都分为基本数据类型和引用数据类型两种</li></ul></li></ol><p><span id="jumpId-JavaType"></span></p><h4 id="java%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B" tabindex="-1">Java数据类型</h4><ol><li>基本数据类型<ul><li>8种基本类型，***注意!!!***下面括号是默认值</li><li>6种数字类型，其中4个整数型<code>byte</code>(0b)&amp;<code>short</code>(0s)&amp;<code>int</code>(0)&amp;<code>long</code>(0L)，2个浮点型<code>float</code>(0.0f)&amp;<code>double</code>(0.0d)</li><li>1种字符类型<code>char</code>(无默认值，大小范围是<code>\u0000</code>-<code>\uffff</code>，等效十进制<code>0</code>~<code>65535</code>)，<br />关于<code>char</code>值需要提一句的是，<code>字符A</code>的十进制值是65，<code>字符a</code>的十进制值是97，后续字母依次每个加一，<br />有个概念叫<code>ascii码</code>有兴趣的可以深入了解下</li><li>1种布尔类型<code>boolean</code>(false)</li><li>***注意!!!***数字类型的结尾字母不需要考虑大小写，都可以</li><li>在计算机中负数由补码进行表示，<code>补码 = 源码取反 + 1</code>，举个例子：<br /><code>22，在计算机中的表示为：00010110；-22，取反：11101001，加1：11101010</code></li></ul></li><li>引用数据类型<ul><li>String、对象、数组、类、接口 &amp; .etc</li><li>所有引用类型的默认值都是<code>null</code></li><li>一个引用变量可以用来引用任何与之兼容的类型</li></ul></li></ol><p><span id="jumpId-JsType"></span></p><h4 id="javascript%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B" tabindex="-1">JavaScript数据类型</h4><ol><li>基本数据类型<ul><li>字符串 <code>String</code>，单引号双引号包裹都可以</li><li>数字 <code>Number</code>，不分整数和小数，说实话这点写起来比Java爽，但是会有危险 😃</li><li>布尔 <code>Boolean</code>， <code>true</code>/<code>false</code></li><li>空 <code>Null</code>，可以将变量值设置为<code>null</code>来清空值</li><li>未定义 <code>Undefined</code>，表示变量不含有值</li><li><code>Symbol</code>，ES6中添加的新类型</li></ul></li><li>引用数据类型<ul><li>对象 <code>Object</code>，由花括号分隔，在括号内部，对象的属性以名称和值对的形式<code>name:value</code>来定义，属性由逗号分隔</li><li>数组 <code>Array</code>，<code>new</code>一个或者直接赋值(隐式创建)</li><li>日期 <code>Date</code>，主要是日期时间的一些操作，不过日常工作中习惯用<code>momentjs</code>处理日期和时间</li></ul></li><li>需要注意的一点<ul><li>JavaScript中的类型是针对值来说，而不是针对变量</li></ul></li></ol><p><span id="jumpId-JavaTypeChange"></span></p><h4 id="java%E4%B8%AD%E7%9A%84%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E8%A7%84%E5%88%99" tabindex="-1">Java中的类型转换规则</h4><ol><li>隐式（默认）类型转换<ul><li>基本类型的默认转换规则 <code>byte/short/char -&gt; int -&gt; long -&gt; float -&gt; double</code></li><li>当<code>byte/short/char</code>相互之间不能转换，它们参与运算首先将转换成<code>int</code>类型再进行运算</li><li>数字和字符串相加，数字会自动装换成字符串</li></ul></li><li>显示（强制）类型转换<ul><li><code>目标类型 变量名 = (目标类型)被转换的类型</code></li><li>容量大的数据类型转换为容量小的数据类型时，要加上强制转换符，但可能造成精度降低或溢出</li><li>有多种类型的数据混合运算时，系统首先自动的将所有数据转换成容量最大的那一种数据类型，然后再进行计算</li></ul></li><li>有趣的例子<ul><li>提问：Java是如何处理强制类型转换的溢出处理呢？</li><li><code>byte  a = (byte)130</code></li><li>答：在计算机中，所有的数据都是存储的补码形式，那么130首先被当成int型存储，四个字节32位，<br />它的补码如下：<code>0000 0000  0000 0000  0000 0000  1000 0010</code>，转换为byte类型，进行截取，<br />高字节部分去除，保留低字节部分，得到转换为byte类型的补码为：<code>1000 0010</code>，<br />我们将其转换为源码：补码（1000 0010）-&gt; 反码（1000 0001）-&gt; 原码（1111 1110）为 -126，所以最后的答案是-126</li><li><a href="https://www.cnblogs.com/baby-lily/p/10658051.html" target="_blank">参考资料</a></li></ul></li></ol><p><span id="jumpId-JsTypeChange"></span></p><h4 id="javascript%E4%B8%AD%E7%9A%84%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E8%A7%84%E5%88%99" tabindex="-1">JavaScript中的类型转换规则</h4><p>JavaScript是弱类型语言，变量类型完全由当前值决定，这种类型就叫弱类型。<br />首先要记住的是，JavaScript中的类型转化，它得到的结果永远是基本类型，也就可以分为两种情况，<br />基本类型和基本类型之间的转化，引用类型转化为基本类型，不可能出现基本类型转化为引用类型，<br />类型转化出现最多就是转化成<code>Number</code>, <code>String</code>, 和<code>Boolean</code></p><h3 id="java%E4%B8%AD%E7%9A%84%E4%B8%BB%E5%8A%A8%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95" tabindex="-1">Java中的主动类型转换常用方法</h3><p><a href="https://blog.csdn.net/weixin_42103983/article/details/106750921" target="_blank">这里感觉可以另外写一个专题博文，后期再补档，到时候再补个链接过来，有兴趣的同学可以先阅读这篇文章</a></p><h3 id="javascript%E4%B8%AD%E7%9A%84%E4%B8%BB%E5%8A%A8%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95" tabindex="-1">JavaScript中的主动类型转换常用方法</h3><ol><li>将其他类型值（数字、布尔、日期）转字符串，一把都是用<code>toString()</code></li><li>字符串转数字，<code>parseInt()</code>转换成整数，<code>parseFloat()</code>转换成浮点数，整数和浮点数都是<code>Number</code>类型</li><li>将布尔转换成数字可以使用方法<code>Number()</code></li><li>当JavaScript尝试操作一个<em><strong>错误</strong></em>的数据类型时，会自动转换为<em><strong>正确</strong></em>的数据类型<ul><li>举个例子：<code>5 + null  // 返回数字5，null 转换为 0</code></li><li>举个例子：<code>'5' + null  // 返回字符串5null，null 转换为 'null'</code></li><li>其他例子就是字符串类型数字和数字类型数字之间的运算都会转成数字运算</li></ul></li><li>将其他类型值转换成布尔值类型<ul><li>原始类型值转换方法，这六个类型<code>undefined/null/-0/0/NaN/空字符串</code>全部为<code>false</code>，其他全部为<code>true</code></li><li>所有对象的布尔值都是<code>true</code>，甚至<code>false</code>对应的布尔值对象也是<code>true</code>，空对象空数组也都是<code>true</code></li><li>举个例子：<code>console.log(Boolean(new Boolean(false)))  // 输出true</code></li></ul></li><li>隐式类型的转换是系统进行运算时自动进行的，但是调用的方法都是显式类型转换的方法<ul><li>一元运算符<code>+</code>可以隐式的将字符串转为数字，如果变量不能转换成数字，他仍会成为一个数字，但值会是<code>NaN</code>，<br />举个例子：<code>var a = 5; console.log(c = +a);  // 输出数字5</code>，<br />举个例子：<code>var b = 'abc'; console.log(b = +abc);  // 输出NaN</code></li><li><code>- * / %</code>（减号、乘号、除号、取余运算符）运算时会通过<code>Number()</code>把数据转换成<code>Number</code>类型后，再进行运算</li><li><code>&amp;&amp; || !</code>（与或非运算符）运算时会通过<code>Boolean()</code>把数据转换成<code>Boolean</code>类型后，再进行运算</li><li><code>&lt; &gt; &lt;= &gt;= == !=</code>（比较运算符）运算时，当数字和字符串比较大小时，会隐示将字符串转换成<code>Number</code>类型进行比较，<br />而当字符串和字符串比较大小时，则比较的是<code>ascii码</code>的大小，最后返回的则是布尔值</li><li>***注意!!!***比较运算符中<code>undefined</code>和<code>null</code>是不会发生类型转换的，只有相等的值会返回<code>true</code></li><li><em><strong>注意!!!</strong></em> 全局<code>isNaN()</code>函数会将测试值通过<code>Number()</code>转换为数字，然后对其进行测试，<br />但是<code>Number.isNaN()</code>不会将值转换为数字，并且不会为任何非数字类型的值返回<code>true</code></li></ul></li><li>如果用<code>==</code>来判断两个数值是否相等，由于会发生隐式类型转换，所以是非常存在非常大的漏洞的，<br />为了解决这一问题，引入了<code>===</code>（绝对等于）和<code>!==</code>（绝对不等于）</li><li><a href="https://www.runoob.com/js/js-type-conversion.html" target="_blank">参考资料一</a>、<a href="https://www.jb51.net/article/136520.htm" target="_blank">参考资料二</a></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2021/2021-10/variable-datatype.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[nvm安装以及管理多版本node教程]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/nvm-install" />
                <id>tag:https://fx67ll.xyz,2021-11-22:nvm-install</id>
                <published>2021-11-22T09:55:29+08:00</published>
                <updated>2025-07-02T20:24:32+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月19日</strong></em></p><h3 id="%E5%AE%89%E8%A3%85nvm%E3%80%81node%E3%80%81npm" tabindex="-1">安装nvm、node、npm</h3><ol><li>下载nvm安装包，推荐使用1.1.7，我个人使用1.1.8会有中文乱码的报错</li><li>点击exe文件，注意修改nvm的安装根目录以及node的安装根目录，后者是以后管理多版本node的源文件储存地址</li><li>打开系统cmd，依次输入一下目录<ul><li><code>nvm -version</code> 检查nvm是否安装成功</li><li><code>nvm install (你需要安装的node版本号)</code> 可以去官网查看 <a href="https://nodejs.org/zh-cn/download/releases/" target="_blank">以往版本列表</a></li><li>注意！！！上一步会直接同时安装相对应版本npm</li><li><code>nvm use (你需要安装的node版本号)</code> 一般还会弹出系统权限使用框，一定要点通过</li><li><code>node -v</code> 检查node是否安装成功</li><li><code>npm -v</code> 检查npm是否安装成功</li></ul></li></ol><h4 id="%E4%B8%A4%E4%B8%AA%E7%89%88%E6%9C%AC%E5%8F%B7%E9%83%BD%E5%8F%AF%E4%BB%A5%E5%87%BA%E6%9D%A5%E4%BA%86%EF%BC%8C%E8%AF%B4%E6%98%8Envm%E7%AE%A1%E7%90%86node%E7%89%88%E6%9C%AC%E5%B0%B1%E5%B7%B2%E7%BB%8F%E6%88%90%E5%8A%9F%E4%BA%86" tabindex="-1">两个版本号都可以出来了，说明nvm管理node版本就已经成功了</h4><h3 id="%E7%AE%A1%E7%90%86node%E7%89%88%E6%9C%AC" tabindex="-1">管理node版本</h3><ol><li>一般需要安装什么版本安装多少node都可以，只需要使用<code>nvm list</code>，就可查看当前所有安装版本的node</li><li>如果需要删除什么版本的ndoe，可以使用命令<code>nvm uninstall</code></li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llNode/blob/main/node-blog/install-nvm.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[IDEA 2021.2.3 安装与破解教程]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/idea-install" />
                <id>tag:https://fx67ll.xyz,2021-11-22:idea-install</id>
                <published>2021-11-22T09:48:53+08:00</published>
                <updated>2025-07-02T11:34:00+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月18日</strong></em></p><p>首先说明，大部分情况下，花10块钱都可以在淘宝找人直接帮你完美解决这个资源问题<br />所以千万不要相信一些所谓的百度结果，一般都是花费了时间却最后无法解决破解的问题</p><p>我相信任何一个想要学习软件开发的人一定要明白一个道理，那就是授人以鱼不如授人以渔<br />所以本文主要是为了给大家解决安装和破解的一个思路问题，不是给大家提供一个一条龙服务，所以想要看这篇文章解决所有问题的同学可以直接关闭了</p><h3 id="%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E5%8C%85" tabindex="-1">下载安装包</h3><p>建议各位同学先学习一个科学上网，不然官网的下载速度一定会让你绝望<br />当然也可以在百度网盘上找资源，相信这点应该不难<br />下载的版本看你的需要，我是用于Java的开发，所以下的是64位的windows安装版本，注意这个版本是可以30天试用的</p><h4 id="%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80%EF%BC%9Aidea-2021.2.3-%E5%AE%98%E7%BD%91%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80" tabindex="-1">下载地址：<a href="https://www.jetbrains.com/idea/download/#section=windows" target="_blank">IDEA 2021.2.3 官网下载地址</a></h4><h3 id="%E7%A0%B4%E8%A7%A3%E6%95%B4%E4%BD%93%E6%80%9D%E8%B7%AF" tabindex="-1">破解整体思路</h3><ol><li>上面下载的时候我有提到30天的试用期，所以对于这个漏洞，可以利用的就是类似百度网盘的无限加速，这里是无限试用</li><li>完成安装后，进入软件的欢迎界面，这里是会提示你输入激活码之类的界面，如果你的界面上没有三十天试用的按钮，那你就需要去淘宝搞个激活码，很便宜，一般不超过十元</li><li>在百度网盘下载该 <a href="https://pan.baidu.com/s/1zbB-YgAauq-lNC2b54bvxw" target="_blank">资源</a>（提取码: <code>nnij</code>），将无限试用的破解拖入主界面中，注意要拖两次，之后会提示是否重启，点击是即可</li><li>重启完之后再主界面按顺序点击<code>Help -&gt; Eval Rest -&gt; reset -&gt; yes</code>，注意勾选<code>Auto reset before per restart</code>选项即可</li><li>如果没有任何报错，理论上已经破解完毕，之后等第二天再次查看试用时间，如果一直是三十天即可认定破解成功</li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2021/2021-10/install-idea.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Java开发环境配置]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/java-dev-environment" />
                <id>tag:https://fx67ll.xyz,2021-11-22:java-dev-environment</id>
                <published>2021-11-22T09:12:14+08:00</published>
                <updated>2025-07-02T11:32:40+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p><em><strong>注意！！！由于近期在做博文搬迁，说明一下本文在其他公共博文平台的初次发布时间为：2021年10月18日</strong></em></p><p>从初次接触代码到现在已经过去很多年了，但是对于任何一个java的初学者来说，jdk的安装与环境配置都是最为基础的内容<br />所以这里记录一下，供初学者参考，如果有错误请各位指正</p><h3 id="%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E5%8C%85" tabindex="-1">下载安装包</h3><ol><li>安装过程全部下一步即可，注意第二步的安装目录，可以自定义想要安装的目录，但是一定要做好记录，后续配置环境变量会需要使用</li><li>其他没有什么好说的，主要是版本问题，高版本商用会有收费问题，个人学习者不担心，所以建议使用java8的版本</li><li>不过需要注意的是java后续高版本的新特性就不可以使用了</li></ol><h4 id="%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80%EF%BC%9Ajdk-%E5%AE%98%E7%BD%91%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80" tabindex="-1">下载地址：<a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html" target="_blank">JDK 官网下载地址</a></h4><h3 id="%E9%85%8D%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F" tabindex="-1">配置环境变量</h3><ol><li>win10环境下直接全局搜索<em><strong>环境变量</strong></em>，会直接打开系统的环境变量配置对话框</li><li>在系统变量的配置区域，添加<code>JAVA_HOME</code>变量，该变量值为<code>D:\Java\jdk1.8.0_191</code><ul><li>注意！！！这里的变量值是我的电脑上java的安装目录，各位需要改为自己的目录</li></ul></li><li>在系统变量的配置区域，添加<code>CLASSPATH</code>，该变量值为<code>.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;</code><ul><li>注意！！！这里的变量值开头的小数点不要丢掉了</li></ul></li><li>在用户变量的配置区域，修改已经存在<code>PATH</code>变量，添加以下两个值<ul><li>值一：<code>%JAVA_HOME%\bin</code></li><li>值二：<code>%JAVA_HOME%\jre\bin</code></li></ul></li></ol><h3 id="%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83%E6%98%AF%E5%90%A6%E9%85%8D%E7%BD%AE%E6%88%90%E5%8A%9F" tabindex="-1">测试环境是否配置成功</h3><ol><li>关闭所有对话框，包括cmd框</li><li>输入<code>java -version</code>，如果显示当前版本号信息，说明安装成功</li><li>如果安装失败，请重新检查之前的步骤是否都正确</li></ol><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llJava/blob/main/java-blog/2021/2021-10/install-jdk.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[记录一下本站使用的一些随机图API]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/api-random-image" />
                <id>tag:https://fx67ll.xyz,2021-11-21:api-random-image</id>
                <published>2021-11-21T17:53:22+08:00</published>
                <updated>2025-07-02T11:28:32+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>本主题的提供者提供了一个非常好的随机图API，但是貌似有时候会挂，所以自己又找了一些有意思的随机图API，在这里做个记录，供后续维护使用</p><p><em><strong>注意！！！本文链接最后测试时间----2021年11月21日</strong></em></p><ol><li><p>主题作者Tagaki的API（有时候会挂）</p><ul><li><a href="https://api.lixingyong.com/api/images" target="_blank">https://api.lixingyong.com/api/images</a></li></ul></li><li><p>保罗API</p><ul><li><a href="https://api.paugram.com/help/wallpaper" target="_blank">使用文档</a></li><li><a href="https://api.paugram.com/wallpaper/?source=sina" target="_blank">https://api.paugram.com/wallpaper/?source=sina</a></li></ul></li><li><p>暗鸦API</p><ul><li><a href="https://img.r10086.com/" target="_blank">使用文档</a></li><li>由于分类太细了，自行去站点查看</li></ul></li><li><p>墨天逸API</p><ul><li><a href="https://api.mtyqx.cn/" target="_blank">使用文档</a></li><li><a href="https://api.mtyqx.cn/api/random.php" target="_blank">https://api.mtyqx.cn/api/random.php</a></li><li><a href="https://api.mtyqx.cn/tapi/random.php" target="_blank">https://api.mtyqx.cn/tapi/random.php</a></li></ul></li><li><p>姬长信API</p><ul><li><a href="https://api.isoyu.com/#/%E5%A3%81%E7%BA%B8%E6%A8%A1%E5%9D%97" target="_blank">使用文档</a></li><li><a href="https://api.isoyu.com/aipc_animation.php" target="_blank">https://api.isoyu.com/aipc_animation.php</a></li></ul></li><li><p>Unsplash API</p><ul><li><a href="https://source.unsplash.com/" target="_blank">使用文档</a></li><li><a href="https://source.unsplash.com/random" target="_blank">https://source.unsplash.com/random</a></li><li><a href="https://source.unsplash.com/user/erondu" target="_blank">https://source.unsplash.com/user/erondu</a></li><li>功能挺多的，自行去站点查看</li></ul></li><li><p>樱花API</p><ul><li><a href="https://www.dmoe.cc/" target="_blank">使用文档</a></li><li><a href="https://www.dmoe.cc/random.php" target="_blank">https://www.dmoe.cc/random.php</a></li></ul></li><li><p>EEE.DOG API</p><ul><li><a href="https://www.eee.dog/tech/rand-pic-api.html" target="_blank">使用文档</a></li><li><a href="https://api.yimian.xyz/img" target="_blank">https://api.yimian.xyz/img</a></li><li>这个图不错但是每日调用上限100且尺寸不太合适做PC壁纸</li></ul></li><li><p>东方Project API</p><ul><li><a href="https://img.paulzzh.com/" target="_blank">使用文档</a></li><li><a href="https://img.paulzzh.com/touhou/random" target="_blank">https://img.paulzzh.com/touhou/random</a></li></ul></li></ol><p><a href="https://www.cnblogs.com/zaxtyson/p/11628746.html" target="_blank">参考文档 – 15个随机图片API</a></p><p>我是 <a href="https://fx67ll.com" target="_blank">fx67ll.com</a>，如果您发现本文有什么错误，欢迎在评论区讨论指正，感谢您的阅读！<br />如果您喜欢这篇文章，欢迎访问我的 <a href="https://github.com/fx67ll/fx67llHalo/blob/main/halo-blog/2021/2021-11/random-picture.md" target="_blank">本文github仓库地址</a>，为我点一颗Star，Thanks~ 😃<br /><em><strong>转发请注明参考文章地址，非常感谢！！！</strong></em></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Code is my life ~]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/code-is-my-life" />
                <id>tag:https://fx67ll.xyz,2021-11-21:code-is-my-life</id>
                <published>2021-11-21T01:55:56+08:00</published>
                <updated>2025-07-02T11:28:24+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>深夜弄完了整个站点的更新，本来想迁移到.com域名上，奈何我真的很喜欢自己做的第一个网页，所以还是放在.xyz域名上，原来的导航迁移到nav.fx67ll.com二级域名下。</p><p>其实一直很想自己手撸一个博客站点，只是做了很久，对自己的设计水平给跪下了，简单的做了几个页面完全没有美感，最后放弃了选择直接在一些大的博客网站上写文章。</p><p>前几天偶然逛github看见了Halo这个开源的博客系统，点进去看了几个Demo，顿时被吸引，于是便开始构思整理迁移我的N多站点，终于在今天完成了所有的事情，虽然只是做了一些简单的系统运维，但还是有小小的成就感的。</p><p>想到了之前把自己github上的项目运用jenkins完成了和自己服务器的持续构建任务，节省了我大量的手动部署时间，真的超爽，我爱这个开源世界！</p><p>希望未来自己能够逐渐成为贡献开源世界的一员，作为一个希望靠技术吃一辈子饭的人，这大概是我人生中最重要的目标之一了，继续努力吧~</p><p>已经很久没熬夜了，身体第一，我要赶紧碎觉了哈哈哈，晚安 😃</p><p><img src="/upload/2021/11/2014-08-10%20112533-11f9ae7cd3a64b67934e93b7cb6fdc2a.jpg" alt="20140810 112533.jpg" /></p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Hello Halo]]></title>
                <link rel="alternate" type="text/html" href="https://fx67ll.xyz/archives/hello-halo" />
                <id>tag:https://fx67ll.xyz,2021-11-20:hello-halo</id>
                <published>2021-11-20T14:32:00+08:00</published>
                <updated>2025-07-02T11:28:37+08:00</updated>
                <author>
                    <name>fx67ll</name>
                    <uri>https://fx67ll.xyz</uri>
                </author>
                <content type="html">
                        <![CDATA[<h2 id="hello-halo" tabindex="-1">Hello Halo</h2><p>如果你看到了这一篇文章，那么证明你已经安装成功了，感谢使用 <a href="https://halo.run" target="_blank">Halo</a> 进行创作，希望能够使用愉快。</p><h2 id="%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5" tabindex="-1">相关链接</h2><ul><li>官网：<a href="https://halo.run" target="_blank">https://halo.run</a></li><li>文档：<a href="https://docs.halo.run" target="_blank">https://docs.halo.run</a></li><li>社区：<a href="https://bbs.halo.run" target="_blank">https://bbs.halo.run</a></li><li>主题仓库：<a href="https://halo.run/themes.html" target="_blank">https://halo.run/themes.html</a></li><li>开源地址：<a href="https://github.com/halo-dev/halo" target="_blank">https://github.com/halo-dev/halo</a></li></ul><p>在使用过程中，有任何问题都可以通过以上链接找寻答案，或者联系我们。</p><blockquote><p>这是一篇自动生成的文章，请删除这篇文章之后开始你的创作吧！</p></blockquote>]]>
                </content>
            </entry>
</feed>
