<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
    <channel>
            <title>龙儿之家</title>
            <link>https://halo.huangge1199.cn</link>
                <description>一个热衷于做小码农的程序媛！！！</description>
        <generator>Halo-Plus 1.1.4</generator>
        <lastBuildDate>Wed, 25 Feb 2026 16:54:00 CST</lastBuildDate>
                <item>
                    <title>
                        <![CDATA[OpenClaw安装与飞书机器人配置]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/openclaw安装与飞书机器人配置</link>
                    <description>
                            <![CDATA[<h1 id="前言">前言</h1><p>🎉 OpenClaw是一款开源免费的AI助手，支持WhatsApp/Telegram/微信等多种聊天平台的自动化。本文将详细介绍如何安装OpenClaw并配置飞书机器人，让你轻松拥有自己的智能助手！</p><h1 id="系统要求">系统要求</h1><ul><li>Node 22+（安装脚本会在缺失时自动安装）</li><li>macOS、Linux 或 Windows</li><li>仅从源码构建时需要 pnpm</li></ul><blockquote><p>注意：在 Windows 上，强烈建议在 WSL2 下运行 OpenClaw。</p></blockquote><h1 id="安装openclaw">安装OpenClaw</h1><h2 id="1使用安装脚本推荐">1、使用安装脚本（推荐）</h2><p>🚀 安装脚本是安装OpenClaw的推荐方式，它可以一步完成Node检测、安装和初始配置，超级方便！</p><h3 id="macos--linux--wsl2">macOS / Linux / WSL2：</h3><pre><code class="language-bash">curl -fsSL https://clawd.org.cn/install.sh | bash</code></pre><h3 id="windows-powershell">Windows (PowerShell)：</h3><pre><code class="language-powershell">iwr -useb https://clawd.org.cn/install.ps1 | iex</code></pre><p>执行安装脚本后的界面：</p><p><img src="https://img.huangge1199.cn/blog/install-openclaw-feishu/2026-02-25-10-07-57-image.png" alt="" /></p><blockquote><p>提示：如果想跳过初始配置，只安装二进制文件，可以使用 <code>--no-onboard</code> 参数。</p></blockquote><h2 id="2使用npm--pnpm安装">2、使用npm / pnpm安装</h2><p>如果你已经有Node 22+，并且想自行管理安装：</p><h3 id="npm">npm：</h3><pre><code class="language-bash">npm install -g openclaw-cn@latest</code></pre><blockquote><p>注意：如果遇到sharp构建错误，可以使用环境变量 <code>SHARP_IGNORE_GLOBAL_LIBVIPS=1</code> 强制使用预构建二进制文件。</p></blockquote><h3 id="pnpm">pnpm：</h3><pre><code class="language-bash">pnpm add -g openclaw-cn@latestpnpm approve-builds -g        # 批准 openclaw-cn、node-llama-cpp、sharp 等openclaw-cn onboard --install-daemon</code></pre><blockquote><p>注意：pnpm 要求显式批准包含构建脚本的包。</p></blockquote><h2 id="3从源码构建">3、从源码构建</h2><p>适用于贡献者或想从本地代码运行的用户：</p><pre><code class="language-bash">git clone https://github.com/jiulingyun/openclaw-cn.gitcd openclawpnpm installpnpm ui:buildpnpm buildpnpm link --globalopenclaw-cn onboard --install-daemon</code></pre><h1 id="验证安装">验证安装</h1><p>✅ 安装完成后，验证一切正常运行：</p><pre><code class="language-bash">openclaw-cn doctor         # 检查配置问题openclaw-cn status         # 网关状态openclaw-cn dashboard      # 打开浏览器管理界面</code></pre><h1 id="配置飞书机器人">配置飞书机器人</h1><p>🤖 现在我们来配置飞书机器人，让OpenClaw能够在飞书中为我们服务！</p><h2 id="1创建飞书应用">1、创建飞书应用</h2><ol><li>打开 <a href="https://open.feishu.cn/">飞书开放平台</a>，使用飞书账号登录</li><li>点击「创建企业自建应用」，填写应用名称和描述</li></ol><p>飞书开放平台创建应用页面：</p><p><img src="https://img.huangge1199.cn/blog/install-openclaw-feishu/image.png" alt="alt text" /></p><ol start="3"><li>在应用的「凭证与基础信息」页面，复制 App ID 和 App Secret</li></ol><p>凭证与基础信息页面：</p><p><img src="https://img.huangge1199.cn/blog/install-openclaw-feishu/image-1.png" alt="alt text" /></p><ol start="4"><li>在「权限管理」页面，批量导入以下权限配置：</li></ol><pre><code class="language-json">{  &quot;scopes&quot;: {    &quot;tenant&quot;: [      &quot;aily:file:read&quot;,      &quot;aily:file:write&quot;,      &quot;application:application.app_message_stats.overview:readonly&quot;,      &quot;application:application:self_manage&quot;,      &quot;application:bot.menu:write&quot;,      &quot;cardkit:card:write&quot;,      &quot;contact:user.employee_id:readonly&quot;,      &quot;corehr:file:download&quot;,      &quot;docs:document.content:read&quot;,      &quot;event:ip_list&quot;,      &quot;im:chat&quot;,      &quot;im:chat.access_event.bot_p2p_chat:read&quot;,      &quot;im:chat.members:bot_access&quot;,      &quot;im:message&quot;,      &quot;im:message.group_at_msg:readonly&quot;,      &quot;im:message.group_msg&quot;,      &quot;im:message.p2p_msg:readonly&quot;,      &quot;im:message:readonly&quot;,      &quot;im:message:send_as_bot&quot;,      &quot;im:resource&quot;,      &quot;sheets:spreadsheet&quot;,      &quot;wiki:wiki:readonly&quot;    ],    &quot;user&quot;: [      &quot;aily:file:read&quot;,      &quot;aily:file:write&quot;,      &quot;im:chat.access_event.bot_p2p_chat:read&quot;    ]  }}</code></pre><blockquote><p>注意：<code>im:message.group_msg</code> 权限（获取群组中所有消息，属于敏感权限）允许机器人接收群组中所有消息（不仅仅是 @机器人的）。如果需要配置 <code>requireMention: false</code> 让机器人无需 @ 也能响应，则必须添加此权限。</p></blockquote><ol start="5"><li>在「应用能力 &gt; 机器人」页面开启机器人能力并配置机器人名称</li></ol><p>机器人能力配置页面：</p><p><img src="https://img.huangge1199.cn/blog/install-openclaw-feishu/image-2.png" alt="alt text" /></p><ol start="6"><li>在「事件订阅」页面选择「使用长连接接收事件（WebSocket 模式）」，并添加事件：<code>im.message.receive_v1</code>（接收消息）</li></ol><p>事件订阅配置页面：</p><p><img src="https://img.huangge1199.cn/blog/install-openclaw-feishu/image-4.png" alt="alt text" /></p><blockquote><p>重要提醒：在配置事件订阅前，请务必确保已完成以下步骤：</p><ul><li>运行 <code>openclaw-cn channels add</code> 添加了 Feishu 渠道</li><li>网关处于启动状态（可通过 <code>openclaw-cn gateway status</code> 检查状态）</li></ul></blockquote><ol start="7"><li>在「版本管理与发布」页面创建版本并提交审核发布</li></ol><h2 id="2添加飞书渠道">2、添加飞书渠道</h2><h3 id="通过命令行添加">通过命令行添加</h3><p>🔧 运行以下命令，根据提示粘贴 App ID 和 App Secret：</p><pre><code class="language-bash">openclaw-cn channels add</code></pre><p>选择 Feishu，然后输入你在第一步获取的凭证即可。</p><h3 id="通过配置文件配置">通过配置文件配置</h3><p>编辑 <code>~/.openclaw/openclaw.json</code>：</p><pre><code class="language-json">{  &quot;channels&quot;: {    &quot;feishu&quot;: {      &quot;enabled&quot;: true,      &quot;dmPolicy&quot;: &quot;pairing&quot;,      &quot;accounts&quot;: {        &quot;main&quot;: {          &quot;appId&quot;: &quot;cli_xxx&quot;,          &quot;appSecret&quot;: &quot;xxx&quot;,          &quot;botName&quot;: &quot;我的AI助手&quot;        }      }    }  }}</code></pre><h3 id="通过环境变量配置">通过环境变量配置</h3><pre><code class="language-bash">export FEISHU_APP_ID=&quot;cli_xxx&quot;export FEISHU_APP_SECRET=&quot;xxx&quot;</code></pre><h2 id="3启动并测试">3、启动并测试</h2><ol><li>启动网关：</li></ol><pre><code class="language-bash">openclaw-cn gateway</code></pre><ol start="2"><li>发送测试消息</li></ol><p>在飞书中找到你创建的机器人，发送一条消息。</p><ol start="3"><li>配对授权</li></ol><p>默认情况下，机器人会回复一个配对码。你需要批准此代码：</p><pre><code class="language-bash">openclaw-cn pairing approve feishu &lt;配对码&gt;</code></pre><p>批准后即可正常对话。</p><h1 id="openclaw功能介绍">OpenClaw功能介绍</h1><p>🌟 OpenClaw作为一款开源免费的AI助手，具有以下强大特点：</p><ul><li>📱 <strong>多平台支持</strong>：通过飞书、WhatsApp、Telegram、Discord、Slack 或 iMessage 与它对话</li><li>🧠 <strong>持久记忆</strong>：记住你的一切并成为独一无二的你的AI</li><li>🌐 <strong>浏览器控制</strong>：可以浏览网页、填写表单、从任何网站提取数据</li><li>💻 <strong>完整系统访问</strong>：读写文件、运行Shell命令、执行脚本</li><li>🧩 <strong>技能与插件</strong>：使用社区技能扩展或自己构建</li></ul><h1 id="管理与维护">管理与维护</h1><p>🔍 日常管理与维护命令：</p><h2 id="查看网关状态">查看网关状态</h2><pre><code class="language-bash">openclaw-cn gateway status</code></pre><h2 id="重启网关">重启网关</h2><pre><code class="language-bash">openclaw-cn gateway restart</code></pre><h2 id="查看实时日志">查看实时日志</h2><pre><code class="language-bash">openclaw-cn logs --follow</code></pre><h2 id="打开管理界面">打开管理界面</h2><pre><code class="language-bash">openclaw-cn dashboard</code></pre><h1 id="环境变量配置">环境变量配置</h1><p>如果你需要自定义运行时路径，可以使用以下环境变量：</p><ul><li><code>OPENCLAW_HOME</code>：设置基于主目录的内部路径</li><li><code>OPENCLAW_STATE_DIR</code>：设置可变状态的存储位置</li><li><code>OPENCLAW_CONFIG_PATH</code>：设置配置文件位置</li></ul><h1 id="常见问题排查">常见问题排查</h1><p>🐛 常见问题及解决方案：</p><h2 id="path-诊断与修复">PATH 诊断与修复</h2><p>快速诊断：</p><pre><code class="language-bash">node -vnpm -vnpm prefix -gecho &quot;$PATH&quot;</code></pre><p>如果 <code>$(npm prefix -g)/bin</code>（macOS/Linux）或 <code>$(npm prefix -g)</code>（Windows）不在你的 <code>$PATH</code> 中，Shell 将无法找到全局 npm 二进制文件（包括 openclaw-cn）。</p><p>修复 — 将以下内容添加到你的 Shell 启动文件（~/.zshrc 或 ~/.bashrc）：</p><pre><code class="language-bash">export PATH=&quot;$(npm prefix -g)/bin:$PATH&quot;</code></pre><p>在 Windows 上，将 <code>npm prefix -g</code> 的输出添加到 PATH 中。然后打开一个新终端。</p><h1 id="总结">总结</h1><p>🎊 恭喜你完成了 OpenClaw 的安装和飞书机器人的配置！现在你已经拥有了一个功能强大的 AI 助手，可以在飞书中为你提供各种服务。</p><p>OpenClaw 不仅支持飞书，还可以与其他多个聊天平台集成，为你带来全方位的 AI 辅助体验。随着你对它的使用，你会发现它越来越了解你，成为你工作和生活中的得力助手。</p><p>如果你在使用过程中遇到任何问题，可以查看官方文档或社区论坛获取帮助。祝你使用愉快！</p>]]>
                    </description>
                    <pubDate>Wed, 25 Feb 2026 10:00:00 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[RustFS从安装到使用及从MinIO迁移的完整指南]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/rustfs从安装到使用及从minio迁移的完整指南</link>
                    <description>
                            <![CDATA[<h1 id="rustfs现代化的分布式文件系统">RustFS：现代化的分布式文件系统</h1><p>RustFS是一个用Rust语言开发的高性能分布式文件系统，具有高可靠性、强一致性和出色的性能。本文将详细介绍RustFS的安装、使用方法，以及如何从MinIO迁移到RustFS。</p><h2 id="一rustfs简介">一、RustFS简介</h2><p>RustFS是基于Rust语言构建的新一代分布式文件系统，它利用Rust的内存安全特性和并发优势，提供了以下核心特性：</p><ul><li><strong>高性能</strong>：采用异步I/O和零拷贝技术，提供出色的读写性能</li><li><strong>强一致性</strong>：实现了严格的一致性模型，确保数据可靠性</li><li><strong>高可用</strong>：支持数据复制和自动故障转移</li><li><strong>易于扩展</strong>：可以轻松添加新节点以扩展存储容量和性能</li><li><strong>安全</strong>：基于Rust的内存安全特性，减少了常见的安全漏洞</li></ul><h2 id="二rustfs安装">二、RustFS安装</h2><p>官方的安装文档比较全，我这里仅仅是记录下自己安装的过程以及遇到的问题。</p><h3 id="21-docker-compose部署">2.1 docker-compose部署</h3><p>我这边是在<code>FnOS</code>系统中使用<code>docker-compose</code>的方式部署的，下面是<code>compose</code>文件内容：</p><pre><code class="language-yaml">version: &quot;3&quot;services:  # RustFS main service  rustfs:    image: rustfs/rustfs:latest    container_name: rustfs    ports:      - &quot;19000:9000&quot; # S3 API port      - &quot;19001:9001&quot; # Console port    environment:      - RUSTFS_ADDRESS=0.0.0.0:9000      - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001      - RUSTFS_CONSOLE_ENABLE=true      - RUSTFS_EXTERNAL_ADDRESS=:9000 # Same as internal since no port mapping      - RUSTFS_CORS_ALLOWED_ORIGINS=*      - RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*      - RUSTFS_ACCESS_KEY=rustfsadmin      - RUSTFS_SECRET_KEY=rustfsadmin      - RUSTFS_OBS_LOGGER_LEVEL=info      - RUSTFS_TLS_PATH=/opt/tls      - RUSTFS_OBS_ENDPOINT=http://otel-collector:4317    volumes:      - ./data:/data      - ./logs:/app/logs    # restart: unless-stopped    healthcheck:      test:        [          &quot;CMD&quot;,          &quot;sh&quot;, &quot;-c&quot;,          &quot;curl -f http://localhost:9000/health &amp;&amp; curl -f http://localhost:9001/rustfs/console/health&quot;        ]      interval: 30s      timeout: 10s      retries: 3      start_period: 40s</code></pre><h3 id="22-启动报错permission-denied">2.2 启动报错（&quot;Permission denied&quot;）</h3><p>通过GitHub上的issues找到了解决方法，将数据和日志目录的所有者更改为10001。</p><p>进到所在目录，执行下面的命令：</p><pre><code class="language-shell"># 修改前检查ls -l# 修改chown -R 10001:10001 data/ logs/# 修改后确认ls -l</code></pre><p>命令执行过程：</p><p><img src="https://img.huangge1199.cn/blog/rustfs-install-use-migrate-from-minio/2025-12-16-14-29-22-image.png" alt="" /></p><blockquote><p>参考地址：<a href="https://github.com/rustfs/rustfs/issues/987">Operator Managed Cluster fails to run · Issue #987 · rustfs/rustfs · GitHub</a></p></blockquote><h2 id="23-部署后确认">2.3 部署后确认</h2><p>docker启动后无报错，在浏览器输入 <code>http://192.168.188.2:19001/</code></p><p><img src="https://img.huangge1199.cn/blog/rustfs-install-use-migrate-from-minio/2025-12-16-14-35-43-image.png" alt="" /></p><p>出现这个说明安装成功，账号/密码：rustfsadmin/rustfsadmin</p><h2 id="三从minio迁移到rustfs">三、从MinIO迁移到RustFS</h2><h3 id="51-迁移准备">5.1 迁移准备</h3><ol><li>确保RustFS服务已正常运行</li><li>安装MinIO客户端 <code>mc</code></li><li>备份MinIO数据（建议）</li></ol><h3 id="52-迁移方法">5.2 迁移方法</h3><p>使用以下命令迁移：</p><pre><code class="language-bash"># 使用mc客户端同步数据mc alias set minio http://minio-server:9000 minioadmin minioadminmc alias set rustfs http://rustfs-server:19000 rustfsadmin rustfsadmin# 同步数据mc mirror --recursive minio/my-bucket rustfs/my-bucket</code></pre><blockquote><p>rustfs/my-bucket 要先在rustfs中建好存储桶my-bucket</p></blockquote><h3 id="53-迁移验证">5.3 迁移验证</h3><pre><code class="language-bash"># 验证文件数量echo &quot;MinIO文件数量：&quot;mc ls --recursive minio/my-bucket | wc -lecho &quot;RustFS文件数量：&quot;rishfs ls --recursive /minio-migrated/ | wc -l# 验证文件完整性（随机选择几个文件）mc cat minio/my-bucket/path/to/file.txt | md5sumrishfs get /minio-migrated/path/to/file.txt - | md5sum</code></pre><blockquote><p>也可以通过web查看确认</p></blockquote><h2 id="四总结">四、总结</h2><p>RustFS是一个功能强大、性能出色的分布式文件系统，适用于各种规模的存储需求。通过本文的介绍，您应该已经掌握了RustFS的安装、配置、使用以及从MinIO迁移数据的方法。</p>]]>
                    </description>
                    <pubDate>Tue, 16 Dec 2025 10:00:00 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Mac双开微信终极指南：一台电脑轻松登录两个微信账号]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/mac双开微信终极指南一台电脑轻松登录两个微信账号</link>
                    <description>
                            <![CDATA[<h1 id="-为什么需要双开微信">🔥 为什么需要双开微信？</h1><p>在这个数字化时代，我们常常需要在工作和生活之间切换。一个微信账号用于工作沟通，一个用于私人聊天，这种分离能让我们更好地平衡工作与生活。但Mac系统默认只允许运行一个微信实例，这给很多用户带来了不便。今天，我将教你<strong>3种简单高效的方法</strong>，让你在Mac上轻松双开微信！</p><h1 id="-前置准备">📋 前置准备</h1><p>在开始之前，请确保你已经：</p><ul><li>安装了微信官方应用（可从<a href="https://mac.weixin.qq.com/">微信官网</a>下载）</li><li>具备基本的终端操作知识</li><li>拥有管理员权限（需要使用sudo命令）</li></ul><h1 id="-双开步骤">🚀 双开步骤</h1><p>下面的主要步骤都是在终端中执行的</p><p><img src="https://img.huangge1199.cn/blog/mac-double-wechat/2025-12-13-16-45-42-image.png" alt="终端执行界面示意图" /></p><h2 id="-步骤1检查系统是否已安装-plistbuddy">🔍 步骤1：检查系统是否已安装 PlistBuddy</h2><pre><code class="language-shell">ls -l /usr/libexec/PlistBuddy</code></pre><p>如果返回类似下图的文件信息，则说明PlistBuddy已安装，直接跳至步骤3</p><p><img src="https://img.huangge1199.cn/blog/mac-double-wechat/2025-12-13-16-45-18-image.png" alt="PlistBuddy存在的终端输出" /></p><h2 id="-步骤2安装-xcode-命令行工具">🛠️ 步骤2：安装 Xcode 命令行工具</h2><pre><code class="language-shell">xcode-select --install</code></pre><p>执行后会弹出安装窗口，按照提示完成安装即可。</p><h2 id="-步骤3复制微信应用">📁 步骤3：复制微信应用</h2><pre><code class="language-shell">sudo cp -R /Applications/WeChat.app /Applications/WeChat2.app</code></pre><p>执行成功后，在「应用程序」文件夹中会出现一个名为 <code>WeChat2.app</code> 的新应用</p><p><img src="https://img.huangge1199.cn/blog/mac-double-wechat/2025-12-13-16-52-23-image.png" alt="应用程序文件夹中的双微信" /></p><h2 id="-步骤4修改-bundle-identifier关键步骤">🔧 步骤4：修改 Bundle Identifier（关键步骤）</h2><pre><code class="language-shell">sudo /usr/libexec/PlistBuddy -c &quot;Set :CFBundleIdentifier com.tencent.xinWeChat2&quot; /Applications/WeChat2.app/Contents/Info.plist</code></pre><p>这行命令将 <code>WeChat2.app</code> 的 Bundle Identifier 改为 <code>com.tencent.xinWeChat2</code>，这是双开成功的关键！</p><h3 id="-科普什么是-bundle-identifier">📚 科普：什么是 Bundle Identifier？</h3><p><strong>Bundle Identifier</strong>（简称 <strong>Bundle ID</strong> 或 <strong>包名</strong>）是 Apple 生态系统中用来<strong>唯一标识</strong>应用程序的字符串，相当于应用的「身份证号码」。</p><h4 id="-核心特性">🔑 核心特性</h4><ul><li><strong>唯一性</strong>：整个 App Store 中，任何两个应用都不能有相同的 Bundle ID</li><li><strong>命名规则</strong>：通常采用反向域名表示法，如 <code>com.company.appname</code></li></ul><h4 id="-主要用途">🎯 主要用途</h4><ul><li><strong>系统识别</strong>：帮助 macOS 区分不同应用的数据和沙盒环境</li><li><strong>功能关联</strong>：推送通知、iCloud 同步等功能都需要与特定 Bundle ID 绑定</li><li><strong>版本跟踪</strong>：Apple 用它来识别和跟踪应用的每个版本</li></ul><h2 id="-步骤5重新签名-wechat2app">✅ 步骤5：重新签名 WeChat2.app</h2><pre><code class="language-shell">sudo codesign -vv --deep --strict /Applications/WeChat2.app</code></pre><p>这一步确保修改后的应用能够被系统信任并正常运行。</p><h1 id="-警告与注意事项">⚠️ 警告与注意事项</h1><h2 id="-安全提示">🔒 安全提示</h2><ul><li><strong>谨慎使用sudo命令</strong>：sudo命令赋予你管理员权限，执行错误可能导致系统问题，请仔细核对命令后再执行</li><li><strong>仅使用官方微信</strong>：请确保从<a href="https://mac.weixin.qq.com/">微信官网</a>下载微信，避免使用第三方修改版</li><li><strong>保护隐私</strong>：双开后两个微信账号的数据是隔离的，但仍需注意保护个人隐私</li></ul><h2 id="-版本更新处理">📱 版本更新处理</h2><ul><li>当微信官方发布新版本时，你需要：<ol><li>先更新原微信（WeChat.app）</li><li>重复上述步骤3-5，重新创建和签名WeChat2.app</li><li>旧的WeChat2.app可以直接删除</li></ol></li></ul><h2 id="-如何卸载双开微信">🗑️ 如何卸载双开微信</h2><p>如果不再需要双开功能，可按照以下步骤彻底卸载：</p><pre><code class="language-shell"># 删除复制的微信应用sudo rm -rf /Applications/WeChat2.app</code></pre><h1 id="-使用方法">🎉 使用方法</h1><ol><li>打开「应用程序」文件夹</li><li>同时或分别点击「WeChat.app」和「WeChat2.app」</li><li>分别登录不同的微信账号</li><li>享受高效的双开体验！</li></ol><blockquote><p>💡 <strong>小贴士</strong>：你可以将两个微信应用都拖到 Dock 栏，方便快速访问</p></blockquote><h1 id="-相关推荐">📚 相关推荐</h1><h2 id="-扩展阅读">🔗 扩展阅读</h2><ul><li><a href="https://support.apple.com/zh-cn/guide/terminal/welcome/mac">Mac终端入门指南</a> - Apple官方终端使用教程</li><li><a href="https://support.apple.com/zh-cn/guide/mac-help/mh40616/mac">macOS安全最佳实践</a> - 保护你的Mac安全</li><li><a href="https://kf.qq.com/faq/120911VrYVrA1509097bQ7rE.html">微信官方帮助中心</a> - 微信常见问题解答</li></ul><h2 id="-其他mac实用技巧">💡 其他Mac实用技巧</h2><ul><li><a href="https://support.apple.com/zh-cn/guide/mac-help/mh26782/mac">如何在Mac上录制屏幕</a> - 内置屏幕录制功能使用</li><li><a href="https://support.apple.com/zh-cn/guide/mac-help/mh35916/mac">Mac电池优化指南</a> - 延长电池使用寿命</li><li><a href="https://support.apple.com/zh-cn/guide/mac-help/mh12217/mac">如何清理Mac存储空间</a> - 释放宝贵的磁盘空间</li></ul><h2 id="-更多应用双开方法">🚀 更多应用双开方法</h2><ul><li><strong>使用第三方工具</strong>：如<a href="https://github.com/Ji4n1ng/OpenInTerminal">OpenInTerminal</a>等工具可以简化双开操作</li><li><strong>创建别名</strong>：通过终端别名快速执行双开命令</li><li><strong>使用Automator</strong>：创建自动化工作流实现一键双开</li></ul><h1 id="-常见问题解答">📝 常见问题解答</h1><h2 id="q-双开微信会被封号吗">Q: 双开微信会被封号吗？</h2><p>A: 目前微信官方并没有明确禁止在同一设备上登录多个账号，只要你遵守微信使用条款，正常使用不会被封号。</p><h2 id="q-双开后会影响微信的正常功能吗">Q: 双开后会影响微信的正常功能吗？</h2><p>A: 不会，两个微信账号的功能完全独立，互不影响。</p><h2 id="q-可以三开或更多微信账号吗">Q: 可以三开或更多微信账号吗？</h2><p>A: 理论上可以，只需按照相同步骤创建更多副本（WeChat3.app, WeChat4.app等），并使用不同的Bundle ID即可。</p><h1 id="-总结">📌 总结</h1><p>通过本文的5个简单步骤，你已经学会了如何在Mac上轻松双开微信：</p><ol><li><strong>检查PlistBuddy</strong>：确认系统是否已安装必要工具</li><li><strong>安装Xcode命令行工具</strong>：（如有需要）安装系统开发工具</li><li><strong>复制微信应用</strong>：创建微信的第二个副本</li><li><strong>修改Bundle ID</strong>：关键步骤，赋予新应用唯一标识</li><li><strong>重新签名</strong>：确保应用能被系统信任</li></ol><h1 id="-写在最后">🌟 写在最后</h1><p>双开微信能帮助你更好地平衡工作与生活，提高沟通效率。希望本文对你有所帮助！如果你在操作过程中遇到任何问题，欢迎在评论区留言交流。</p><blockquote><p>📢 <strong>温馨提示</strong>：技术在不断发展，本文方法可能会随系统更新而变化。建议收藏本文，以便后续查阅最新内容。</p></blockquote><p>感谢阅读，祝你使用愉快！🎉</p>]]>
                    </description>
                    <pubDate>Sat, 13 Dec 2025 10:00:00 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[【避坑指南】震惊！90%Java开发者都不知道的NullPointerException隐藏陷阱]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/-bi-keng-zhi-nan--zhen-jing-90java-kai-fa-zhe-dou-bu-zhi-dao-de-nullpointerexception-yin-cang-xian-jing</link>
                    <description>
                            <![CDATA[<p>```在日常Java开发中，<code>NullPointerException</code>（NPE）是我们最常遇到的运行时异常之一。特别是字符串操作时，一不小心就会掉进NPE的陷阱。今天就来彻底剖析这些隐藏的坑点，让你的代码更加健壮！</p><h2 id="--最经典的陷阱-a-equals-b-">🔥 最经典的陷阱：a.equals(b)</h2><pre><code class="language-java">String a = null;String b = "hello";// 这是NPE的经典场景！a.equals(b); // 抛出 NullPointerException</code></pre><p><strong>真相揭秘</strong>：</p><ul><li><code>a</code>&nbsp;可以为null，但调用方法时会抛出NPE</li><li><code>b</code>&nbsp;可以为null，通常返回&nbsp;<code>false</code>（前提是a不为null）</li></ul><p><strong>安全写法</strong>：</p><pre><code class="language-java">// 方法1：让常量在前（推荐）"hello".equals(a); // 安全，a为null时返回false// 方法2：使用Objects.equals（最安全）Objects.equals(a, b); // 两者都为null时返回true// 方法3：显式null检查if (a != null) {    a.equals(b);}</code></pre><h2 id="--更大的陷阱-a-contains-b-">💥 更大的陷阱：a.contains(b)</h2><p>你以为<code>equals</code>已经很坑了？<code>contains</code>更是坑中之王！</p><pre><code class="language-java">String a = null;String b = "hello";a.contains(b); // NPE！String c = "hello";String d = null;c.contains(d); // 还是NPE！</code></pre><p><strong>残酷真相</strong>：</p><ul><li><code>a</code>&nbsp;不能为null → NPE</li><li><code>b</code>&nbsp;不能为null → NPE</li><li><strong>两个参数都不能为null！</strong></li></ul><p><strong>安全方案</strong>：</p><pre><code class="language-java">// 自定义安全方法public static boolean safeContains(String str, String searchStr) {    return str != null &amp;&amp; searchStr != null &amp;&amp; str.contains(searchStr);}// 使用工具类StringUtils.contains(a, b); // Apache Commons Lang</code></pre><h2 id="--字符串操作中的NPE地雷阵">🚨 字符串操作中的NPE地雷阵</h2><p>下面这些方法，只要调用者为null，统统NPE！</p><h3 id="比较相关">比较相关</h3><pre><code class="language-java">String str = null;str.compareTo("abc");       // 💥 NPEstr.compareToIgnoreCase("abc"); // 💥 NPEstr.contentEquals("abc");   // 💥 NPE</code></pre><h3 id="查找相关">查找相关</h3><pre><code class="language-java">str.indexOf("a");           // 💥 NPEstr.lastIndexOf("a");       // 💥 NPE  str.startsWith("a");        // 💥 NPEstr.endsWith("a");          // 💥 NPE</code></pre><h3 id="操作相关">操作相关</h3><pre><code class="language-java">str.substring(1);           // 💥 NPEstr.charAt(0);              // 💥 NPEstr.length();               // 💥 NPEstr.trim();                 // 💥 NPEstr.toUpperCase();          // 💥 NPE</code></pre><h3 id="正则相关">正则相关</h3><pre><code class="language-java">str.matches("regex");       // 💥 NPEstr.replaceAll("a", "b");   // 💥 NPEstr.split(",");             // 💥 NPE</code></pre><h3 id="空检查-讽刺吧--">空检查（讽刺吧？）</h3><pre><code class="language-java">str.isEmpty();              // 💥 NPEstr.isBlank();              // 💥 NPE</code></pre><h2 id="---全方位防御方案">🛡️ 全方位防御方案</h2><h3 id="方案1-Objects工具类-JDK原生-">方案1：Objects工具类（JDK原生）</h3><pre><code class="language-java">import java.util.Objects;Objects.equals(a, b);              // 比较安全Objects.toString(str, "default");  // 转字符串安全</code></pre><h3 id="方案2-Apache-Commons-Lang">方案2：Apache Commons Lang</h3><pre><code class="language-java">import org.apache.commons.lang3.StringUtils;StringUtils.equals(a, b);          // 比较安全StringUtils.contains(a, b);        // 包含检查安全StringUtils.isEmpty(a);            // 空检查安全StringUtils.isBlank(a);            // 空白检查安全</code></pre><h3 id="方案3-防御性编程模式">方案3：防御性编程模式</h3><pre><code class="language-java">// 三元运算符String safeStr = (str != null) ? str : "";boolean result = safeStr.contains("abc");// Optional优雅处理Optional.ofNullable(str)    .map(s -&gt; s.contains("abc"))    .orElse(false);// 提前返回if (str == null) {    return false; // 或抛出业务异常}return str.contains("abc");</code></pre><h3 id="方案4-静态代码分析工具">方案4：静态代码分析工具</h3><ul><li><strong>SonarQube</strong>：检测潜在的NPE风险</li><li><strong>SpotBugs</strong>：静态分析找出NPE问题</li><li><strong>IDEA Inspections</strong>：IDE自动提示</li></ul><h2 id="--最佳实践总结">📊 最佳实践总结</h2><table><thead><tr><th>场景</th><th>危险写法</th><th>安全写法</th></tr></thead><tbody><tr><td>字符串比较</td><td><code>a.equals(b)</code></td><td><code>Objects.equals(a, b)</code></td></tr><tr><td>包含检查</td><td><code>a.contains(b)</code></td><td><code>StringUtils.contains(a, b)</code></td></tr><tr><td>空检查</td><td><code>a.isEmpty()</code></td><td><code>StringUtils.isEmpty(a)</code></td></tr><tr><td>方法调用</td><td><code>a.length()</code></td><td><code>Optional.ofNullable(a).map(String::length)</code></td></tr></tbody></table><h2 id="--终极建议">💡 终极建议</h2><ol><li><strong>养成习惯</strong>：总是考虑参数为null的情况</li><li><strong>使用工具</strong>：优先选择null安全的工具类</li><li><strong>代码审查</strong>：将NPE风险纳入代码审查清单</li><li><strong>单元测试</strong>：覆盖null边界情况的测试用例</li></ol><p>记住：<strong>在Java世界里，唯一不会抛出NPE的字符串方法，就是你没有在null上调用的那个方法！</strong></p><hr><p><strong>思考题</strong>：你的项目中还有哪些隐藏的NPE陷阱？欢迎在评论区分享交流！</p>]]>
                    </description>
                    <pubDate>Mon, 01 Dec 2025 13:26:29 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Git误提交敏感文件的完全清除方法]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/git误提交敏感文件的完全清除方法</link>
                    <description>
                            <![CDATA[<h2 id="问题背景">问题背景</h2><p>在日常开发过程中，我们经常会遇到这样的情况：不小心将包含敏感信息的文件提交到了Git仓库中。这些敏感文件可能包括：</p><ul><li><strong>配置文件</strong>：包含数据库密码、API密钥、服务器地址等</li><li><strong>环境变量文件</strong>：如 <code>.env</code> 文件，包含各种环境配置和密钥</li><li><strong>证书文件</strong>：SSL证书、私钥文件等</li><li><strong>日志文件</strong>：可能包含用户信息或系统敏感数据</li><li><strong>临时文件</strong>：包含调试信息或测试数据的文件</li></ul><p>一旦这些文件被提交到Git仓库，即使后续删除了这些文件，它们仍然会存在于Git的历史记录中。这意味着：</p><ol><li><strong>安全风险</strong>：任何有权访问仓库的人都可以通过Git历史查看这些敏感信息</li><li><strong>合规问题</strong>：可能违反公司的安全政策或法规要求</li><li><strong>持续暴露</strong>：即使在后续提交中删除了文件，历史记录中仍然存在</li><li><strong>克隆风险</strong>：每次克隆仓库时，这些敏感信息都会被下载</li></ol><p>因此，仅仅删除文件是不够的，我们需要从Git的整个历史记录中彻底清除这些敏感文件的所有痕迹。</p><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/git-history-sensitive-files.png" alt="Git历史中的敏感文件示意图"><br><em>图1：Git历史记录中残留的敏感文件示意图</em></p><h2 id="解决方案">解决方案</h2><blockquote><p><strong>💡 Windows 用户特别说明</strong>：本文所有命令均在 Windows 环境下测试通过。建议使用 PowerShell 或 Git Bash 执行命令。如果使用 CMD，某些命令可能需要调整语法。</p></blockquote><h3 id="方法一-使用-git-filter-branch">方法一：使用 git filter-branch</h3><p><code>git filter-branch</code> 是Git内置的工具，可以重写Git历史记录。</p><h4 id="删除特定文件">删除特定文件</h4><pre><code class="language-bash"># 从所有提交中删除指定文件git filter-branch --force --index-filter \  'git rm --cached --ignore-unmatch path/to/sensitive-file.txt' \  --prune-empty --tag-name-filter cat -- --all</code></pre><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/git-filter-branch-process.png" alt="git filter-branch 执行过程"><br><em>图2：git filter-branch 命令执行过程示意图</em></p><h4 id="删除包含敏感信息的目录">删除包含敏感信息的目录</h4><pre><code class="language-bash"># 删除整个目录git filter-branch --force --index-filter \  'git rm -r --cached --ignore-unmatch sensitive-directory/' \  --prune-empty --tag-name-filter cat -- --all</code></pre><h4 id="清理和强制推送">清理和强制推送</h4><pre><code class="language-bash"># 清理引用git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdingit reflog expire --expire=now --allgit gc --prune=now# 强制推送到远程仓库git push origin --force --allgit push origin --force --tags</code></pre><h3 id="方法二-使用-BFG-Repo-Cleaner">方法二：使用 BFG Repo-Cleaner</h3><p>BFG Repo-Cleaner 是一个专门用于清理Git仓库的工具，比 <code>git filter-branch</code> 更快更简单。</p><h4 id="安装-BFG">安装 BFG</h4><pre><code class="language-bash"># Windows 用户推荐方法：直接下载 JAR 文件# 使用 PowerShell 下载Invoke-WebRequest -Uri "https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar" -OutFile "bfg.jar"# 或使用 curl (如果已安装)curl -L -o bfg.jar https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar# macOS 用户可使用 Homebrewbrew install bfg</code></pre><blockquote><p><strong>Windows 注意</strong>：确保已安装 Java 8 或更高版本。可通过 <code>java -version</code> 检查。</p></blockquote><h4 id="使用-BFG-删除文件">使用 BFG 删除文件</h4><pre><code class="language-bash"># 克隆一个裸仓库git clone --mirror https://github.com/username/repo.git# 删除特定文件java -jar bfg.jar --delete-files sensitive-file.txt repo.git# 或删除包含特定文本的文件java -jar bfg.jar --delete-files "*.{env,config}" repo.git# 删除大于指定大小的文件java -jar bfg.jar --strip-blobs-bigger-than 50M repo.git</code></pre><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/bfg-workflow.png" alt="BFG Repo-Cleaner 工作流程"><br><em>图3：BFG Repo-Cleaner 清理敏感文件的工作流程</em></p><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/powershell-bfg-command.png" alt="Windows PowerShell 中执行 BFG 命令"><br><em>图4：在 Windows PowerShell 中执行 BFG 命令的截图示例</em></p><h4 id="清理和推送">清理和推送</h4><pre><code class="language-bash">cd repo.gitgit reflog expire --expire=now --all &amp;&amp; git gc --prune=now --aggressivegit push</code></pre><h3 id="方法三-使用-git-filter-repo">方法三：使用 git filter-repo</h3><p><code>git filter-repo</code> 是现代化的替代方案，推荐用于复杂的仓库清理任务。</p><h4 id="安装-git-filter-repo">安装 git filter-repo</h4><pre><code class="language-bash"># Windows 用户推荐使用 pip 安装pip install git-filter-repo# 如果使用 Python 3，可能需要使用 pip3pip3 install git-filter-repo# macOS 用户可使用 Homebrewbrew install git-filter-repo# Windows 用户也可以手动安装# 1. 下载 git-filter-repo 脚本# 2. 将其放在 PATH 环境变量包含的目录中</code></pre><blockquote><p><strong>Windows 特别提醒</strong>：</p><ul><li>确保 Python 和 pip 已正确安装并添加到 PATH 环境变量</li><li>在 PowerShell 中执行 <code>python --version</code> 和 <code>pip --version</code> 验证安装</li><li>如果遇到权限问题，可能需要以管理员身份运行 PowerShell</li></ul></blockquote><h4 id="删除文件和目录">删除文件和目录</h4><pre><code class="language-bash"># 删除特定文件git filter-repo --path path/to/sensitive-file.txt --invert-paths# 删除多个文件git filter-repo --path sensitive-file1.txt --path sensitive-file2.txt --invert-paths# 删除目录git filter-repo --path sensitive-directory/ --invert-paths# 使用正则表达式删除文件git filter-repo --path-regex '.*\.env$' --invert-paths</code></pre><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/git-filter-repo-execution.png" alt="git filter-repo 命令执行效果"><br><em>图5：git filter-repo 命令执行效果对比图</em></p><h4 id="替换敏感内容">替换敏感内容</h4><pre><code class="language-bash"># 替换文件中的敏感文本git filter-repo --replace-text replacements.txt</code></pre><p>其中 <code>replacements.txt</code> 文件内容示例：</p><pre><code>password123==&gt;***REMOVED***api_key_abc123==&gt;***REMOVED***regex:secret_[a-zA-Z0-9]+==&gt;***REMOVED***</code></pre><h2 id="注意事项">注意事项</h2><h3 id="---重要警告">⚠️ 重要警告</h3><ol><li><p><strong>备份仓库</strong>：在执行任何历史重写操作之前，务必备份整个仓库</p><pre><code class="language-bash"># Windows PowerShell 中执行git clone --mirror original-repo.git backup-repo.git# 或者直接复制整个项目文件夹作为备份Copy-Item -Path "C:\path\to\your\repo" -Destination "C:\path\to\backup\repo" -Recurse</code></pre></li><li><p><strong>团队协调</strong>：历史重写会改变所有提交的SHA值，需要通知所有团队成员重新克隆仓库</p></li><li><p><strong>强制推送风险</strong>：使用 <code>--force</code> 推送会覆盖远程仓库，确保团队成员已保存本地更改</p></li></ol><h3 id="操作步骤建议">操作步骤建议</h3><ol><li><p><strong>先在本地测试</strong>：在本地副本上测试清理操作，确认效果后再应用到主仓库</p></li><li><p><strong>检查清理结果</strong>：</p><pre><code class="language-bash"># 检查文件是否完全删除git log --all --full-history -- path/to/sensitive-file.txt# Windows PowerShell 中搜索残留内容git grep -i "sensitive_keyword" $(git rev-list --all)# 如果上述命令在 PowerShell 中出现问题，可使用：git rev-list --all | ForEach-Object { git grep -i "sensitive_keyword" $_ }</code></pre><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/verification-commands.png" alt="验证清理结果的命令执行"><br><em>图6：验证敏感文件是否完全清除的命令执行结果</em></p></li><li><p><strong>通知团队成员</strong>：</p><pre><code class="language-bash"># 团队成员需要执行的操作（Windows/Linux/macOS 通用）git fetch origingit reset --hard origin/maingit clean -fd</code></pre></li></ol><h3 id="---Windows-环境特殊注意事项">🖥️ Windows 环境特殊注意事项</h3><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/windows-environment-check.png" alt="Windows 环境配置检查"><br><em>图7：Windows 环境下的必要配置检查界面</em></p><ol><li><p><strong>路径分隔符</strong>：Windows 使用反斜杠 <code>\\</code>，但 Git 命令中建议使用正斜杠 <code>/</code></p><pre><code class="language-bash"># 推荐写法（跨平台兼容）git filter-branch --index-filter 'git rm --cached --ignore-unmatch config/database.yml'# 避免使用（Windows 特有）git filter-branch --index-filter 'git rm --cached --ignore-unmatch config\\database.yml'</code></pre><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/path-separator-comparison.png" alt="路径分隔符对比"><br><em>图8：Windows 下路径分隔符的正确使用方式</em></p></li><li><p><strong>PowerShell 执行策略</strong>：如果遇到脚本执行限制，可临时调整：</p><pre><code class="language-powershell"># 查看当前执行策略Get-ExecutionPolicy# 临时允许脚本执行（管理员权限）Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser</code></pre><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/powershell-execution-policy.png" alt="PowerShell 执行策略设置"><br><em>图9：PowerShell 执行策略设置界面截图</em></p></li><li><p><strong>长路径支持</strong>：Windows 默认路径长度限制为 260 字符，可能影响深层目录操作：</p><pre><code class="language-bash"># 启用长路径支持git config --global core.longpaths true</code></pre></li><li><p><strong>文件权限</strong>：Windows 文件权限与 Unix 系统不同，某些操作可能需要管理员权限</p></li></ol><h3 id="性能考虑">性能考虑</h3><ul><li><strong>大型仓库</strong>：对于大型仓库，推荐使用 <code>git filter-repo</code> 或 BFG，性能更好</li><li><strong>网络带宽</strong>：强制推送会重新上传整个仓库历史，注意网络带宽消耗</li><li><strong>存储空间</strong>：清理后的仓库大小会显著减小，但需要时间进行垃圾回收</li></ul><h2 id="总结">总结</h2><p>清除Git历史中的敏感文件是一个需要谨慎处理的操作。<strong>本文所有方法均在 Windows 环境下验证可行</strong>，Windows 用户可以放心使用。</p><p>根据不同的场景，可以选择合适的工具：</p><ul><li><strong>git filter-branch</strong>：Git内置工具，适合简单的文件删除操作，Windows 兼容性最好</li><li><strong>BFG Repo-Cleaner</strong>：专业的清理工具，操作简单，性能优秀，需要 Java 环境</li><li><strong>git filter-repo</strong>：现代化的解决方案，功能强大，推荐用于复杂场景，需要 Python 环境</li></ul><h3 id="Windows-用户推荐流程">Windows 用户推荐流程</h3><p><img src="https://img.huangge1199.cn/blog/remove-sensitive-files-from-git-history/complete-workflow-diagram.png" alt="Windows 用户完整操作流程图"><br><em>图10：Windows 用户清除 Git 敏感文件的完整操作流程图</em></p><ol><li><strong>环境准备</strong>：确保 Git、Java（BFG需要）、Python（filter-repo需要）已正确安装</li><li><strong>选择工具</strong>：新手推荐 BFG，高级用户推荐 git filter-repo</li><li><strong>使用 PowerShell</strong>：推荐使用 PowerShell 而非 CMD 执行命令</li><li><strong>路径格式</strong>：统一使用正斜杠 <code>/</code> 作为路径分隔符</li></ol><h4 id="三种方法对比表">三种方法对比表</h4><table><thead><tr><th>维度</th><th>git filter-branch</th><th>BFG Repo-Cleaner</th><th>git filter-repo</th></tr></thead><tbody><tr><td>安装要求</td><td>无需额外安装</td><td>需要 Java 8+</td><td>需要 Python 3/pip</td></tr><tr><td>速度</td><td>慢</td><td>很快</td><td>快</td></tr><tr><td>操作难度</td><td>高（命令复杂）</td><td>低（命令简单）</td><td>中（灵活但需要理解）</td></tr><tr><td>覆盖能力</td><td>删除文件/目录，重写历史</td><td>删除文件、删除大文件、简单替换</td><td>删除文件/目录、文本替换、复杂历史改写</td></tr><tr><td>变更可控性</td><td>一般，容易误操作</td><td>较好，默认保护引用</td><td>很好，支持多种过滤器与 dry-run</td></tr><tr><td>适用场景</td><td>小型仓库、简单删除</td><td>大型仓库、批量清理、大文件问题</td><td>复杂场景、精确替换与改写</td></tr><tr><td>推荐指数</td><td>★★★☆☆</td><td>★★★★★</td><td>★★★★☆</td></tr></tbody></table><h3 id="最佳实践">最佳实践</h3><ol><li><strong>预防为主</strong>：使用 <code>.gitignore</code> 文件防止敏感文件被提交</li><li><strong>定期检查</strong>：定期审查仓库中的文件，及时发现问题</li><li><strong>权限管理</strong>：合理设置仓库访问权限，限制敏感信息的暴露范围</li><li><strong>自动化检测</strong>：使用工具如 <code>git-secrets</code> 或 <code>truffleHog</code> 自动检测敏感信息</li></ol><p>记住，一旦敏感信息被推送到公共仓库，就应该立即更换相关的密钥、密码等凭据，因为无法保证没有人已经获取了这些信息。</p>]]>
                    </description>
                    <pubDate>Thu, 04 Sep 2025 13:32:46 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Java事务回滚详解]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/java-shi-wu-hui-gun-xiang-jie</link>
                    <description>
                            <![CDATA[<h2 id="一什么是事务回滚">一、什么是事务回滚？</h2><p>事务回滚指的是：当执行过程中发生异常时，之前对数据库所做的更改全部撤销，数据库状态恢复到事务开始前的状态。这是数据库“原子性”原则的体现。</p><hr /><h2 id="二spring-中的-transactional-默认行为">二、Spring 中的 <code>@Transactional</code> 默认行为</h2><p>在 Spring 中，使用注解方式开启事务非常简单：</p><pre><code class="language-java">@Transactionalpublic void doSomething() {    // 执行数据库操作}</code></pre><p>此时的默认行为是：</p><ul><li><strong>事务会在方法成功执行后提交；</strong></li><li><strong>遇到 <code>RuntimeException</code> 或 <code>Error</code>，会自动回滚；</strong></li><li><strong>遇到 <code>Checked Exception</code>（即编译时异常），不会自动回滚。</strong></li></ul><p>例如：</p><pre><code class="language-java">@Transactionalpublic void test1() {    throw new RuntimeException(); // ✅ 会回滚}@Transactionalpublic void test2() throws Exception {    throw new Exception(); // ❌ 不会回滚}</code></pre><hr /><h2 id="三使用-rollbackfor-让事务回滚受检异常">三、使用 <code>rollbackFor</code> 让事务回滚受检异常</h2><p>如果你希望事务在<strong>任何异常</strong>发生时都回滚，包括受检异常，比如 <code>IOException</code>、<code>SQLException</code>，就需要显式指定：</p><pre><code class="language-java">@Transactional(rollbackFor = Exception.class)public void test3() throws Exception {    throw new Exception(); // ✅ 会回滚}</code></pre><ul><li><code>rollbackFor</code> 的值可以是一个或多个异常类；</li><li>你可以根据需要选择只对某些异常类型回滚，其他的则不回滚。</li></ul><hr /><h2 id="四rollbackfor-和-rollbackon-的区别">四、<code>rollbackFor</code> 和 <code>rollbackOn</code> 的区别</h2><table><thead><tr><th>特性</th><th><code>rollbackFor</code></th><th><code>rollbackOn</code></th></tr></thead><tbody><tr><td>适用范围</td><td>Spring</td><td>Java EE / JTA</td></tr><tr><td>包名</td><td><code>org.springframework.transaction.annotation.Transactional</code></td><td><code>javax.transaction.Transactional</code></td></tr><tr><td>默认行为</td><td>回滚 <code>RuntimeException</code></td><td>不回滚任何异常</td></tr><tr><td>明确配置后</td><td>可回滚任何指定异常</td><td>可回滚任何指定异常</td></tr></tbody></table><h3 id="示例比较">示例比较：</h3><h4 id="spring-中的写法">Spring 中的写法：</h4><pre><code class="language-java">import org.springframework.transaction.annotation.Transactional;@Transactional(rollbackFor = Exception.class)public void springTransaction() throws Exception {    throw new Exception(&quot;测试受检异常&quot;);}</code></pre><h4 id="jtajava-ee中的写法">JTA（Java EE）中的写法：</h4><pre><code class="language-java">import javax.transaction.Transactional;@Transactional(rollbackOn = Exception.class)public void jtaTransaction() throws Exception {    throw new Exception(&quot;测试受检异常&quot;);}</code></pre><p>注意：<strong>使用的是不同的注解类，不能混用！</strong></p><hr /><h2 id="五常见误区">五、常见误区</h2><h3 id="-误区1以为所有异常都会触发事务回滚">❌ 误区1：以为所有异常都会触发事务回滚</h3><p>Spring 默认只回滚 <code>RuntimeException</code>，不会回滚 <code>Exception</code>（受检异常）。这是导致事务未回滚的最常见原因。</p><h3 id="-误区2以为-transactional-可以应用于任何方法">❌ 误区2：以为 <code>@Transactional</code> 可以应用于任何方法</h3><p>只有被 Spring 容器管理（即被 Spring 扫描并代理）的类中的 <code>public</code> 方法，<code>@Transactional</code> 才有效。如果你在 <code>private</code> 方法上加了注解，是不会生效的。</p><h3 id="-误区3使用错误的注解类">❌ 误区3：使用错误的注解类</h3><p>Spring 和 JTA 的 <code>@Transactional</code> 注解来自不同的包，使用时务必导入正确：</p><ul><li>Spring: <code>org.springframework.transaction.annotation.Transactional</code></li><li>JTA: <code>javax.transaction.Transactional</code></li></ul><hr /><h2 id="六小结">六、小结</h2><h3 id="常见问题与解决方式">常见问题与解决方式</h3><table><thead><tr><th>问题</th><th>默认行为</th><th>解决方式</th></tr></thead><tbody><tr><td><strong>事务不回滚受检异常</strong></td><td>❌ 不回滚</td><td>✅ 添加 <code>rollbackFor = Exception.class</code>（Spring）或 <code>rollbackOn = Exception.class</code>（JTA）</td></tr><tr><td><strong>事务注解不生效</strong></td><td>❌ 方法不是 <code>public</code>，类未被 Spring 管理</td><td>✅ 保证类被 Spring 扫描，方法为 <code>public</code></td></tr><tr><td><strong>导入错误注解</strong></td><td>❌ 使用了错误的 <code>@Transactional</code> 注解</td><td>✅ 使用正确包名下的注解（见下表）</td></tr></tbody></table><h3 id="spring-与-jta-的-transactional-对比">Spring 与 JTA 的 <code>@Transactional</code> 对比</h3><table><thead><tr><th>特性</th><th>Spring</th><th>JTA（Java EE）</th></tr></thead><tbody><tr><td>注解类全名</td><td><code>org.springframework.transaction.annotation.Transactional</code></td><td><code>javax.transaction.Transactional</code></td></tr><tr><td>默认回滚行为</td><td>回滚 <code>RuntimeException</code>，不回滚 <code>Exception</code></td><td>不回滚任何异常</td></tr><tr><td>控制参数</td><td><code>rollbackFor</code>, <code>noRollbackFor</code> 等</td><td><code>rollbackOn</code>, <code>dontRollbackOn</code></td></tr><tr><td>常见场景</td><td>Spring Boot, Spring MVC 项目</td><td>Java EE, Jakarta EE 应用服务器项目</td></tr><tr><td>建议用法</td><td>用 Spring 的事务注解为主</td><td>仅在 Java EE 项目中使用</td></tr></tbody></table><hr /><h2 id="七结语">七、结语</h2><p>事务控制是保障系统数据一致性的重要手段，理解事务的回滚机制尤为重要。在实际开发中，推荐明确指定异常回滚策略，避免因受检异常不回滚而造成数据异常。</p><p>希望这篇文章能帮你在开发中更精准地使用 <code>@Transactional</code>，写出更健壮、可控的代码。如果你有更多问题，欢迎留言讨论！</p>]]>
                    </description>
                    <pubDate>Tue, 10 Jun 2025 23:20:37 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[青岛旅游规划（AI版本）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/qing-dao-lv-you-gui-hua-ai-ban-ben-</link>
                    <description>
                            <![CDATA[<h1 id="--青岛五日游完整行程攻略">🏨 青岛五日游完整行程攻略</h1><hr><h2 id="--酒店住宿">🏩 酒店住宿</h2><p><strong>海滨花园大酒店</strong></p><ul><li>价格控制在200元/晚</li><li>每天起始和终止均在此酒店</li></ul><hr><h1 id="--Day-1-青岛经典海滨与市区文化游">📅 Day 1：青岛经典海滨与市区文化游</h1><table><thead><tr><th>序号</th><th>景点名称</th><th>门票（元）</th><th>游玩时长</th><th>交通费用（元）</th><th>交通耗时</th><th>游玩时间</th></tr></thead><tbody><tr><td>🏨 酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>—</td><td>—</td><td>08:00 出发</td></tr><tr><td>🏘️ 1</td><td>栈桥（23）</td><td>免费</td><td>1小时</td><td>🚶 0元</td><td>15分钟</td><td>08:15~09:15</td></tr><tr><td>🏞️ 2</td><td>八大峡（17）</td><td>免费</td><td>1小时</td><td>🚶 0元</td><td>10分钟</td><td>09:25~10:25</td></tr><tr><td>🏖️ 3</td><td>第一海水浴场（8）</td><td>免费</td><td>1.5小时</td><td>🚕 15元</td><td>20分钟</td><td>10:45~12:15</td></tr><tr><td>🍜 午餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚕 10元</td><td>15分钟</td><td>12:30~13:30</td></tr><tr><td>🌳 4</td><td>信号山公园山顶（6）</td><td>免费</td><td>1小时</td><td>🚕 15元</td><td>15分钟</td><td>13:45~14:45</td></tr><tr><td>🌲 5</td><td>小鱼山公园山顶（5）</td><td>免费</td><td>1小时</td><td>🚕 15元</td><td>15分钟</td><td>15:00~16:00</td></tr><tr><td>🎡 6</td><td>中山公园太平山观光索道（14）</td><td>40元</td><td>1小时</td><td>🚕 15元</td><td>15分钟</td><td>16:15~17:15</td></tr><tr><td>🍲 晚餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚕 15元</td><td>15分钟</td><td>17:30~18:30</td></tr><tr><td>🏨 返回酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>🚕 15元</td><td>15分钟</td><td>18:45~19:00</td></tr></tbody></table><h3 id="--当日花销汇总">💰 当日花销汇总</h3><ul><li>门票：40元</li><li>餐饮：80元</li><li>交通：115元</li><li><strong>合计：235元</strong></li></ul><hr><h1 id="--Day-2-青岛文化街区与购物小店">📅 Day 2：青岛文化街区与购物小店</h1><table><thead><tr><th>序号</th><th>景点名称</th><th>门票（元）</th><th>游玩时长</th><th>交通费用（元）</th><th>交通耗时</th><th>游玩时间</th></tr></thead><tbody><tr><td>🏨 酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>—</td><td>—</td><td>08:00 出发</td></tr><tr><td>📚 1</td><td>良友书坊（27）</td><td>免费</td><td>1小时</td><td>🚕 15元</td><td>15分钟</td><td>08:15~09:15</td></tr><tr><td>🛍️ 2</td><td>五仁杂货铺（25）</td><td>免费</td><td>0.5小时</td><td>🚶 0元</td><td>10分钟</td><td>09:25~09:55</td></tr><tr><td>🌳 3</td><td>小麦岛公园（41）</td><td>免费</td><td>1小时</td><td>🚌 5元</td><td>20分钟</td><td>10:15~11:15</td></tr><tr><td>🍜 午餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚌 5元</td><td>20分钟</td><td>11:35~12:35</td></tr><tr><td>☕ 4</td><td>年代coffee（20）</td><td>50元</td><td>1小时</td><td>🚶 0元</td><td>10分钟</td><td>12:45~13:45</td></tr><tr><td>🏘️ 5</td><td>拜泉路20号（31）</td><td>免费</td><td>0.5小时</td><td>🚌 5元</td><td>20分钟</td><td>14:05~14:35</td></tr><tr><td>🏪 6</td><td>友客便利店(嘉定路店)（30）</td><td>免费</td><td>0.5小时</td><td>🚶 0元</td><td>10分钟</td><td>14:45~15:15</td></tr><tr><td>🛤️ 7</td><td>琴屿路（22）</td><td>免费</td><td>0.5小时</td><td>🚌 5元</td><td>15分钟</td><td>15:30~16:00</td></tr><tr><td>🍲 晚餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚌 5元</td><td>15分钟</td><td>16:15~17:15</td></tr><tr><td>🏨 返回酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>🚕 15元</td><td>15分钟</td><td>17:30~17:45</td></tr></tbody></table><h3 id="--当日花销汇总-">💰 当日花销汇总</h3><ul><li>门票：0元</li><li>餐饮：80元</li><li>交通：55元</li><li><strong>合计：135元</strong></li></ul><hr><h1 id="--Day-3-青岛山海风光探秘">📅 Day 3：青岛山海风光探秘</h1><table><thead><tr><th>序号</th><th>景点名称</th><th>门票（元）</th><th>游玩时长</th><th>交通费用（元）</th><th>交通耗时</th><th>游玩时间</th></tr></thead><tbody><tr><td>🏨 酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>—</td><td>—</td><td>08:00 出发</td></tr><tr><td>🌊 1</td><td>第三海水浴场（16）</td><td>免费</td><td>1.5小时</td><td>🚕 20元</td><td>20分钟</td><td>08:20~09:50</td></tr><tr><td>🏘️ 2</td><td>太平角四路19号（3）</td><td>免费</td><td>0.5小时</td><td>🚶 0元</td><td>10分钟</td><td>10:00~10:30</td></tr><tr><td>🏘️ 3</td><td>太平角六路5号（29）</td><td>免费</td><td>0.5小时</td><td>🚌 5元</td><td>15分钟</td><td>10:45~11:15</td></tr><tr><td>🍜 午餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚌 5元</td><td>15分钟</td><td>11:30~12:30</td></tr><tr><td>⛰️ 4</td><td>信号山公园山顶（6）</td><td>免费</td><td>1小时</td><td>🚕 15元</td><td>15分钟</td><td>12:45~13:45</td></tr><tr><td>🛤️ 5</td><td>黄县路（9）</td><td>免费</td><td>0.5小时</td><td>🚌 5元</td><td>15分钟</td><td>14:00~14:30</td></tr><tr><td>🛤️ 6</td><td>金口一路（10）</td><td>免费</td><td>0.5小时</td><td>🚌 5元</td><td>15分钟</td><td>14:45~15:15</td></tr><tr><td>🛤️ 7</td><td>金口三路(猫猫台阶)（11）</td><td>免费</td><td>0.5小时</td><td>🚌 5元</td><td>15分钟</td><td>15:30~16:00</td></tr><tr><td>🍲 晚餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚕 15元</td><td>20分钟</td><td>16:20~17:20</td></tr><tr><td>🏨 返回酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>🚕 20元</td><td>20分钟</td><td>17:40~18:00</td></tr></tbody></table><h3 id="--当日花销汇总--">💰 当日花销汇总</h3><ul><li>门票：0元</li><li>餐饮：80元</li><li>交通：90元</li><li><strong>合计：170元</strong></li></ul><hr><h1 id="--Day-4-极地海洋与主题乐园体验">📅 Day 4：极地海洋与主题乐园体验</h1><table><thead><tr><th>序号</th><th>景点名称</th><th>门票（元）</th><th>游玩时长</th><th>交通费用（元）</th><th>交通耗时</th><th>游玩时间</th></tr></thead><tbody><tr><td>🏨 酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>—</td><td>—</td><td>08:00 出发</td></tr><tr><td>🐧 1</td><td>极地海洋公园（2）</td><td>120元</td><td>3小时</td><td>🚕 25元</td><td>25分钟</td><td>08:25~11:25</td></tr><tr><td>🍜 午餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚕 20元</td><td>20分钟</td><td>11:45~12:45</td></tr><tr><td>🎢 2</td><td>方特梦幻王国（38）</td><td>180元</td><td>3小时</td><td>🚕 25元</td><td>25分钟</td><td>13:10~16:10</td></tr><tr><td>🍲 晚餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚕 20元</td><td>20分钟</td><td>16:30~17:30</td></tr><tr><td>🏨 返回酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>🚕 25元</td><td>25分钟</td><td>17:55~18:20</td></tr></tbody></table><h3 id="--当日花销汇总---">💰 当日花销汇总</h3><ul><li>门票：300元</li><li>餐饮：80元</li><li>交通：135元</li><li><strong>合计：515元</strong></li></ul><hr><h1 id="--Day-5-乡村风情与文化深度游">📅 Day 5：乡村风情与文化深度游</h1><table><thead><tr><th>序号</th><th>景点名称</th><th>门票（元）</th><th>游玩时长</th><th>交通费用（元）</th><th>交通耗时</th><th>游玩时间</th></tr></thead><tbody><tr><td>🏨 酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>—</td><td>—</td><td>08:00 出发</td></tr><tr><td>🏘️ 1</td><td>胶州湾水库（7）</td><td>免费</td><td>1小时</td><td>🚕 20元</td><td>20分钟</td><td>08:20~09:20</td></tr><tr><td>🏘️ 2</td><td>灵山湾生态湿地公园（35）</td><td>免费</td><td>2小时</td><td>🚕 20元</td><td>20分钟</td><td>09:40~11:40</td></tr><tr><td>🍜 午餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚕 20元</td><td>20分钟</td><td>12:00~13:00</td></tr><tr><td>🏘️ 3</td><td>老城区（19）</td><td>免费</td><td>2小时</td><td>🚕 20元</td><td>20分钟</td><td>13:20~15:20</td></tr><tr><td>🛤️ 4</td><td>胶州市中心步行街（28）</td><td>免费</td><td>1小时</td><td>🚕 15元</td><td>15分钟</td><td>15:35~16:35</td></tr><tr><td>🍲 晚餐</td><td>裴家小吃店（36）</td><td>40元</td><td>1小时</td><td>🚕 15元</td><td>15分钟</td><td>16:50~17:50</td></tr><tr><td>🏨 返回酒店</td><td>海滨花园大酒店</td><td>—</td><td>—</td><td>🚕 15元</td><td>15分钟</td><td>18:05~18:20</td></tr></tbody></table><h3 id="--当日花销汇总----">💰 当日花销汇总</h3><ul><li>门票：0元</li><li>餐饮：80元</li><li>交通：110元</li><li><strong>合计：190元</strong></li></ul><hr><h1 id="--景点详细介绍">📝 景点详细介绍</h1><h3 id="栈桥-23-">栈桥（23）</h3><p>青岛的标志性建筑，始建于1892年，是青岛最早的码头。桥上可以观赏海景与青岛老城区风光。</p><h3 id="八大峡-17-">八大峡（17）</h3><p>著名的海滨风景区，以奇石、峭壁和大海闻名，是徒步和摄影的好地方。</p><h3 id="第一海水浴场-8-">第一海水浴场（8）</h3><p>青岛最早开放的海水浴场，拥有细腻的沙滩和清澈的海水，夏季非常适合游泳。</p><h3 id="信号山公园山顶-6-">信号山公园山顶（6）</h3><p>登顶后可以俯瞰整个青岛市区和海岸线，风景壮观。</p><h3 id="小鱼山公园山顶-5-">小鱼山公园山顶（5）</h3><p>一处历史悠久的公园，山顶的望海亭是观赏青岛老城风光的好去处。</p><h3 id="中山公园太平山观光索道-14-">中山公园太平山观光索道（14）</h3><p>索道连接中山公园和太平山，乘坐可俯瞰城市风光，适合轻松游览。</p><h3 id="良友书坊-27-">良友书坊（27）</h3><p>青岛的独立书店，文化氛围浓厚，适合文艺青年静心阅读。</p><h3 id="五仁杂货铺-25-">五仁杂货铺（25）</h3><p>一间充满怀旧气息的小店，售卖各种有趣的小物件。</p><h3 id="小麦岛公园-41-">小麦岛公园（41）</h3><p>滨海公园，环境优美，是散步和休闲的好去处。</p><h3 id="年代coffee-20-">年代coffee（20）</h3><p>当地受欢迎的咖啡馆，提供各类特色咖啡和甜点。</p><h3 id="拜泉路20号-31-">拜泉路20号（31）</h3><p>历史街区，保留了许多老青岛的建筑风格。</p><h3 id="友客便利店-嘉定路店--30-">友客便利店(嘉定路店)（30）</h3><p>便捷的购物点，适合补充日常用品。</p><h3 id="琴屿路-22-">琴屿路（22）</h3><p>安静的街道，适合拍照和散步。</p><h3 id="第三海水浴场-16-">第三海水浴场（16）</h3><p>青岛著名的海水浴场之一，环境优美，适合游泳和晒太阳。</p><h3 id="太平角四路19号-3----太平角六路5号-29-">太平角四路19号（3） / 太平角六路5号（29）</h3><p>历史建筑区，体现青岛的德式建筑风格。</p><h3 id="黄县路-9----金口一路-10----金口三路-猫猫台阶--11-">黄县路（9） / 金口一路（10） / 金口三路(猫猫台阶)（11）</h3><p>老城区特色街道，适合感受青岛老城区生活氛围。</p><h3 id="极地海洋公园-2-">极地海洋公园（2）</h3><p>大型海洋主题公园，有企鹅、海豚表演等多种海洋生物展览。</p><h3 id="方特梦幻王国-38-">方特梦幻王国（38）</h3><p>大型主题乐园，适合家庭和年轻人，设施丰富。</p><h3 id="胶州湾水库-7-">胶州湾水库（7）</h3><p>自然风景区，环境幽静，适合短途郊游。</p><h3 id="灵山湾生态湿地公园-35-">灵山湾生态湿地公园（35）</h3><p>自然保护区，鸟类繁多，是生态旅游胜地。</p><h3 id="老城区-19-">老城区（19）</h3><p>青岛最古老的城区，建筑保留了众多德式风格。</p><h3 id="胶州市中心步行街-28-">胶州市中心步行街（28）</h3><p>商业街区，购物和美食聚集地。</p><hr><h1 id="--本次青岛五日游总花销汇总">💰 本次青岛五日游总花销汇总</h1><table><thead><tr><th>类别</th><th>费用（元）</th></tr></thead><tbody><tr><td>门票总计</td><td>340</td></tr><tr><td>餐饮总计</td><td>400</td></tr><tr><td>交通总计</td><td>505</td></tr><tr><td><strong>总计</strong></td><td><strong>1245元</strong></td></tr></tbody></table><hr>]]>
                    </description>
                    <pubDate>Mon, 09 Jun 2025 19:45:44 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[阜新一日游详细行程规划（打车出行 · 起止点：阜新站）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/阜新一日游详细行程规划打车出行起止点阜新站</link>
                    <description>
                            <![CDATA[<h1 id="阜新一日游详细行程规划打车出行--起止点阜新站">阜新一日游详细行程规划（打车出行 · 起止点：阜新站）</h1><p>🕘 出发时间：上午 9:30 到达阜新站<br />🕕 返回时间：预计晚上 19:20 返回阜新站</p><hr /><h2 id="-行程总览">🚗 行程总览</h2><table><thead><tr><th>顺序</th><th>景点</th><th>建议停留时间</th><th>门票</th><th>打车费（元）</th><th>路程时间</th><th>时间段</th></tr></thead><tbody><tr><td>1</td><td>阜新市博物馆</td><td>1 小时</td><td>免费</td><td>12</td><td>10 分钟</td><td>09:30 ~ 10:30</td></tr><tr><td>2</td><td>细河公园</td><td>40 分钟</td><td>免费</td><td>7</td><td>6 分钟</td><td>10:40 ~ 11:20</td></tr><tr><td>🍽️</td><td>午餐：满堂春小馆（细河公园附近）</td><td>40 分钟</td><td>/</td><td>步行</td><td>/</td><td>11:30 ~ 12:10</td></tr><tr><td>3</td><td>阜新玛瑙文化博物馆</td><td>1 小时</td><td>免费</td><td>7</td><td>6 分钟</td><td>12:20 ~ 13:20</td></tr><tr><td>4</td><td>海州露天矿国家矿山公园</td><td>1.5 小时</td><td>免费</td><td>11</td><td>10 分钟</td><td>13:30 ~ 15:00</td></tr><tr><td>5</td><td>三一八公园</td><td>40 分钟</td><td>免费</td><td>9</td><td>8 分钟</td><td>15:10 ~ 15:50</td></tr><tr><td>6</td><td>海州庙</td><td>30 分钟</td><td>免费</td><td>5</td><td>4 分钟</td><td>16:00 ~ 16:30</td></tr><tr><td>7</td><td>人民公园</td><td>40 分钟</td><td>免费</td><td>9</td><td>8 分钟</td><td>16:40 ~ 17:20</td></tr><tr><td>🍽️</td><td>晚餐：老八味馆（人民公园附近）</td><td>40 分钟</td><td>/</td><td>步行</td><td>/</td><td>17:30 ~ 18:10</td></tr><tr><td>8</td><td>解放广场</td><td>20 分钟</td><td>免费</td><td>步行</td><td>10 分钟</td><td>18:20 ~ 18:40</td></tr><tr><td>9</td><td>玉龙公园</td><td>20 分钟</td><td>免费</td><td>10</td><td>10 分钟</td><td>18:50 ~ 19:10</td></tr><tr><td>10</td><td>返回阜新站</td><td>/</td><td>/</td><td>12</td><td>10 分钟</td><td>19:10 ~ 19:20</td></tr></tbody></table><blockquote><p>💰 <strong>总打车费用：约 82 元</strong><br />🕓 <strong>总游玩+交通时间：约 8.5 小时（含吃饭）</strong></p></blockquote><hr /><h2 id="-景点详情含时间段">📍 景点详情（含时间段）</h2><h3 id="1-阜新市博物馆-0930--1030">1️⃣ 阜新市博物馆 （09:30 ~ 10:30）</h3><ul><li>综合性地方博物馆，包含历史、民族、地质文物等。</li><li>⏰ 建议游玩：1 小时</li><li>💵 门票：免费</li></ul><h3 id="2-细河公园-1040--1120">2️⃣ 细河公园 （10:40 ~ 11:20）</h3><ul><li>沿细河建设的城市生态带，适合休闲漫步。</li><li>⏰ 建议游玩：40 分钟</li><li>💵 门票：免费</li></ul><h3 id="-午餐满堂春小馆1130--1210">🍽️ 午餐：满堂春小馆（11:30 ~ 12:10）</h3><ul><li>📍 细河东街与解放大街交口附近</li><li>🍜 地三鲜、小鸡炖蘑菇、酸菜五花肉</li><li>💵 人均：30 ~ 40 元</li></ul><h3 id="3-阜新玛瑙文化博物馆-1220--1320">3️⃣ 阜新玛瑙文化博物馆 （12:20 ~ 13:20）</h3><ul><li>中国独一无二的玛瑙主题展馆。</li><li>⏰ 建议游玩：1 小时</li><li>💵 门票：免费</li></ul><h3 id="4-海州露天矿国家矿山公园-1330--1500">4️⃣ 海州露天矿国家矿山公园 （13:30 ~ 15:00）</h3><ul><li>中国最大的露天煤矿之一，可俯瞰矿坑全貌。</li><li>⏰ 建议游玩：1.5 小时</li><li>💵 门票：免费（内部观光车如使用约 10 元）</li></ul><h3 id="5-三一八公园-1510--1550">5️⃣ 三一八公园 （15:10 ~ 15:50）</h3><ul><li>纪念三一八工人起义的主题红色教育公园。</li><li>⏰ 建议游玩：40 分钟</li><li>💵 门票：免费</li></ul><h3 id="6-海州庙-1600--1630">6️⃣ 海州庙 （16:00 ~ 16:30）</h3><ul><li>历史悠久的庙宇，融合地方信仰文化。</li><li>⏰ 建议游玩：30 分钟</li><li>💵 门票：免费</li></ul><h3 id="7-人民公园-1640--1720">7️⃣ 人民公园 （16:40 ~ 17:20）</h3><ul><li>阜新市内老牌综合性市民公园，树木繁茂、设施完备。</li><li>⏰ 建议游玩：40 分钟</li><li>💵 门票：免费</li></ul><h3 id="-晚餐老八味馆1730--1810">🍽️ 晚餐：老八味馆（17:30 ~ 18:10）</h3><ul><li>📍 人民公园北门附近</li><li>🍜 铁锅炖排骨、东北大拉皮、锅包肉</li><li>💵 人均：40 ~ 50 元</li></ul><h3 id="8-解放广场-1820--1840">8️⃣ 解放广场 （18:20 ~ 18:40）</h3><ul><li>市中心地标，适合拍照与短暂停留。</li><li>⏰ 建议游玩：20 分钟</li><li>💵 门票：免费</li></ul><h3 id="9-玉龙公园-1850--1910">9️⃣ 玉龙公园 （18:50 ~ 19:10）</h3><ul><li>自然景观丰富，适合散步、划船等休闲活动。</li><li>⏰ 建议游玩：20 分钟</li><li>💵 门票：免费</li></ul><hr /><h2 id="-返回阜新站1910--1920">🔁 返回阜新站（19:10 ~ 19:20）</h2><hr /><h2 id="-注意事项">📝 注意事项</h2><ul><li>🧴 建议携带水、充电宝、防晒霜</li><li>📱 建议使用高德或滴滴打车</li><li>☔ 若遇雨天可适度压缩户外景点时间</li></ul>]]>
                    </description>
                    <pubDate>Sat, 31 May 2025 21:22:22 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[GitHub提交代码失败：Couldn't connect to server]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/github提交代码失败couldntconnecttoserver</link>
                    <description>
                            <![CDATA[<h1 id="问题描述">问题描述</h1><p>本次出现的问题如标题所示，在往GitHub提交代码时提交失败了，完整的信息如下：</p><pre><code class="language-shell">fatal: unable to access 'https://github.com/GitHub用户名/仓库名.git/': Failed to connect to github.com port 443 after 21112 ms: Couldn't connect to server</code></pre><h1 id="问题思考">问题思考</h1><p>我们正常在提交代码或者拉取代码时默认会使用HTTPS的形式，如下面似的：</p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-20-17-image.png" alt="" /></p><p>但是这种情况下经常会出现连接失败的情况，这时候我们可以考虑下第二种方法，使用SSH</p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-22-29-image.png" alt="" /></p><p>这种方式下，提交代码和拉取代码的成功率还是提高的，至少我这边基本就没失败过。</p><h1 id="解决办法">解决办法</h1><h2 id="github配置页面进入方式">GitHub配置页面进入方式</h2><p>上面的是我已经配置好的情况，下面我将带着大家完成这个配置过程</p><p>正常未配置时，点击ssh会弹出下面的窗口：</p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-27-07-image.png" alt="" /></p><p>这里就提示我们需要添加一个<code>public key</code>，我们点击红框内的链接就可以跳转过去。</p><p>页面如下：</p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-34-22-image.png" alt="" /></p><blockquote><p>这个页面也可以用下面的办法进入：</p></blockquote><ol><li><p>登录GitHub，点击右上角头像，再点击<code>Settings</code></p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-31-04-image.png" alt="" /></p></li><li><p>页面进来后再点击<code>SSH and GPG keys</code></p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-32-10-image.png" alt="" /></p></li></ol><p>之后，再点击<code>New SSH key</code></p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-35-12-image.png" alt="" /></p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-36-15-image.png" alt="" /></p><p>这地方说明一下，<code>Title</code>可以随意写，<code>Key type</code>直接用默认的就好，<code>Key</code>部分需要你自己生成的。</p><h2 id="key值生成方式">KEY值生成方式</h2><p>生成的方式，需要在你拉取或者提交代码的电脑上做如下操作：</p><p>通过命令行执行：</p><pre><code class="language-shell">ssh-keygen -t rsa -b 4096 -C &quot;your_email@example.com&quot;</code></pre><p>中间一路回车就好，当然如果你不想用默认的，可以自己设置下</p><p>Windows：</p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-51-40-image.png" alt="" /></p><p>Linux：</p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-52-34-image.png" alt="" /></p><p>正常情况下，Windows和Linux都是可以直接执行的，如果提示没有<code>ssh-keygen</code>这个命令，那么你需要安装下<code>OpenSSH</code>。</p><h2 id="配置完成检查">配置完成检查</h2><p>上面Windows和Linux的截图中，红色部分就是你key值存放的文件了，打开那个文件将内容粘贴到key值里面后，点击 <code>Add SSH Key</code>就配置完成了</p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-55-19-image.png" alt="" /></p><p>保存后：</p><p><img src="https://img.huangge1199.cn/blog/githubti-jiao-dai-ma-shi-bai-couldn-t-connect-to-server/2025-05-26-09-56-42-image.png" alt="" /></p><p>接下来就可以在你生成key的电脑上通过SSH的方式拉取代码了</p>]]>
                    </description>
                    <pubDate>Mon, 26 May 2025 09:13:35 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[安装部署Jenkins]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/an-zhuang-bu-shu-jenkins</link>
                    <description>
                            <![CDATA[<h1 id="一-安装">一、安装</h1><h2 id="debain系统安装">debain系统安装</h2><pre><code class="language-shell">wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ &gt; /etc/apt/sources.list.d/jenkins.list'sudo apt-get updatesudo apt-get install jenkins</code></pre><p>使用下面的命令查看启动情况：</p><pre><code class="language-shell">sudo systemctl status jenkins</code></pre><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-14-00-image.png" alt=""></p><h2 id="docker-compose安装">docker-compose安装</h2><blockquote><p>安装后由于一些后续的操作放弃了，比如说工作空间问题、用户权限问题、需要的开发环境问题等等，个人感觉配置起来比较麻烦，就放弃了</p></blockquote><p>docker-compose文件内容如下：</p><pre><code class="language-yml">version: "3"services:  jenkins:    image: jenkins/jenkins:lts-jdk17    container_name: jenkins    restart: on-failure:3    volumes:      - ./home:/var/jenkins_home    ports:      - "8080:8080"</code></pre><h1 id="二-安装后设置向导">二、安装后设置向导</h1><h2 id="解锁Jenkins">解锁Jenkins</h2><p>当您第一次访问新的Jenkins实例时，系统会要求您使用自动生成的密码对其进行解锁。</p><p>浏览器访问&nbsp;<code>http://localhost:8080</code>（或安装时为Jenkins配置的任何端口），并等待&nbsp;<strong>解锁 Jenkins</strong>&nbsp;页面出现。</p><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-15-47-image.png" alt=""></p><p>执行下面的命令查看密码：</p><pre><code class="language-shell">sudo cat /var/lib/jenkins/secrets/initialAdminPassword</code></pre><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-16-17-image.png" alt=""></p><h2 id="安装默认插件">安装默认插件</h2><p>输入密码后点击continue，出现下面的内容，我这边直接使用的<code>安装推荐的插件</code></p><p><img src="https://developer.qcloudimg.com/column/article/1009240/20240119-6de2cad4.png" alt=""></p><p>选择后就开始安装插件了，但是由于默认的Jenkins仓库是国外的，安装插件中途会出现下面的安装失败的情况，类似这样的不用担心，点击下方的重试，继续安装</p><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-19-52-image.png" alt=""></p><p>重复几次后就能安装成功了</p><h2 id="创建管理员用户">创建管理员用户</h2><p>之后出现下面的界面，看界面内容就知道了，这地方需要设置管理员用户名密码了</p><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-21-38-image.png" alt=""></p><p>设置好点击保存并完成</p><h2 id="实例配置">实例配置</h2><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-24-13-image.png" alt=""></p><p>这地方可以不管，直接点击保存并完成</p><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-25-01-image.png" alt=""></p><p>到这里，jenkins算是初步安装完成了，点击开始使用jenkins就好了</p><h1 id="三-其他配置">三、其他配置</h1><h2 id="修改镜像源">修改镜像源</h2><p>前面安装时，提到过，安装插件的时候需要反复重试，在这里可以将插件源改为国内的，这样可以快速安装插件</p><p>在 Jenkins <strong>系统管理 → 插件管理 → 高级</strong>，修改 <strong>“更新站点”</strong>：</p><pre><code class="language-shell">https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json</code></pre><p>由于版本不一样，步骤可能有点不同，这边我将从Jenkins开始来进行截图</p><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-29-11-image.png" alt=""></p><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-29-37-image.png" alt=""></p><p><img src="https://img.huangge1199.cn/blog/an-zhuang-bu-shu-jenkins/2025-04-03-11-30-25-image.png" alt=""></p><h2 id="插件安装">插件安装</h2><p>我这边使用的gitea仓库，额外安装了下面三个插件：</p><ul><li><a href="https://plugins.jenkins.io/gitea">Gitea</a></li><li><a href="https://plugins.jenkins.io/gitea-checks">Gitea Checks</a></li><li><a href="https://plugins.jenkins.io/generic-event">Generic Event</a></li><li><a href="https://plugins.jenkins.io/generic-webhook-trigger">Generic Webhook Trigger Plugin</a></li></ul>]]>
                    </description>
                    <pubDate>Thu, 03 Apr 2025 15:24:22 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[docker部署oracle12c]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/docker-bu-shu-oracle12c</link>
                    <description>
                            <![CDATA[<h1 id="前言">前言</h1><p>近期工作需要，数据库换成了oracle，而我自己没有私有化的oracle数据库，为了之后工作的方便，决定私人部署一个oracle数据库。</p><p>为了方便，还是使用的docker方式部署在nas上面了，看了看docker hub，oracle的镜像全部是6年前的，我这边直接选择了第一个镜像<code>truevoly/oracle-12c</code></p><p>好了，下面正式开始</p><h1 id="安装部署">安装部署</h1><p>我这边直接使用docker命令下载并启动镜像，下面是命令：</p><pre><code class="language-shell"># docker命令docker run -d -p 1521:1521 --name oracle truevoly/oracle-12c# 验证是否启动</code></pre><p><img src="https://img.huangge1199.cn/blog/docker-bu-shu-oracle12c/2025-03-11-14-10-23-image.png" alt=""></p><h1 id="配置">配置</h1><p>为了使用自带的<code>SqlPlus</code>,需要添加一下环境变量，有两种办法，</p><ul><li>进入docker容器内部，直接修改文件</li><li>将文件从docker容器中复制出来，修改后，在拷贝进去</li></ul><h2 id="docker内修改文件">docker内修改文件</h2><p>进入docker容器内部才行，下面是进入的命令：</p><pre><code class="language-shell">docker exec -it 773382d440e8 /bin/bash</code></pre><blockquote><p>注意，代码中的<code>773382d440e8</code>是容器ID，需要改成自己的</p></blockquote><p>进入之后通过<code>vim</code>编辑<code>/etc/profile</code>文件</p><pre><code class="language-shell">vi /etc/profile</code></pre><p>在文件的末尾添加三行：</p><pre><code class="language-shell">export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2export ORACLE_SID=huangge1199export PATH=$ORACLE_HOME/bin:$PATH</code></pre><h2 id="复制docker容器">复制docker容器</h2><p>从docker容器中拷贝文件到当前目录：</p><pre><code class="language-shell">docker cp 773382d440e8:/etc/profile .</code></pre><blockquote><p>注意，代码中的<code>773382d440e8</code>是容器ID，需要改成自己的</p></blockquote><p>本地修改文件</p><p>从本地将文件拷贝回docker容器：</p><pre><code class="language-shell">docker cp profile 773382d440e8:/etc/profile</code></pre><blockquote><p>注意，代码中的<code>773382d440e8</code>是容器ID，需要改成自己的</p></blockquote><h1 id="修改管理密码">修改管理密码</h1><p>上一步已经配置好了，现在就可以使用自带的<code>SqlPlus</code>了。</p><p>首先先进入docker容器：</p><pre><code class="language-shell">docker exec -it 773382d440e8 /bin/bash</code></pre><p>切换用户：</p><pre><code class="language-shell">su oracle</code></pre><p>进入`SQL*Plus``:</p><pre><code class="language-shell">$ORACLE_HOME/bin/sqlplus / as sysdba</code></pre><p>修改system和sys的密码：</p><pre><code class="language-sql">ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;alter user SYSTEM account unlock;alter user system identified by huangge1199;alter user sys identified by huangge1199;</code></pre><p><img src="https://img.huangge1199.cn/blog/docker-bu-shu-oracle12c/2025-03-11-15-37-45-image.png" alt=""></p><h1 id="DataGrip测试连接">DataGrip测试连接</h1><p>DG连接需要一个<code>SID</code>，接着在上一步的SQL里面查询下：</p><pre><code class="language-sql">select name from v$database;</code></pre><p><img src="https://img.huangge1199.cn/blog/docker-bu-shu-oracle12c/2025-03-11-15-40-15-image.png" alt=""></p><p>接下来就是DG的连接设置了：</p><p><img src="https://img.huangge1199.cn/blog/docker-bu-shu-oracle12c/2025-03-11-15-42-25-image.png" alt=""></p>]]>
                    </description>
                    <pubDate>Tue, 25 Mar 2025 14:53:38 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[ollama和open-webui部署ds]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/ollama-he-open-webui-bu-shu-ds</link>
                    <description>
                            <![CDATA[<h1 id="引言">引言</h1><p>最近，deepseek是越来越火，我也趁着这个机会做了下私有化部署，我这边使用的ollama和  open-webui实现的web版本</p><h1 id="ollama">ollama</h1><h2 id="简介">简介</h2><p>Ollama 是一个开源的工具，专门用于简化机器学习和 AI 模型的部署。它提供了一个统一的平台，允许你通过命令行工具创建、管理和更新模型。无论你是想在本地开发环境中运行模型，还是将其部署到云端，Ollama 都可以简化这一过程。</p><p>Ollama 支持多种常见的机器学习模型框架，包括但不限于 TensorFlow、PyTorch、Hugging Face Transformers 等，此外还支持类似 <strong>DeepSeek</strong> 这种自定义的搜索引擎模型。</p><h2 id="核心特性">核心特性</h2><ul><li><strong>易于使用的命令行界面</strong>：Ollama 提供了简单直观的命令行工具，可以通过几条命令就完成模型的创建、启动、更新等操作。</li><li><strong>环境隔离</strong>：Ollama 可以为每个模型提供独立的运行环境，避免了不同模型之间的依赖冲突。</li><li><strong>跨平台支持</strong>：无论你是使用 Linux、Mac 还是 Windows，Ollama 都可以无缝运行。</li><li><strong>自动更新</strong>：Ollama 会自动为模型提供更新，确保你使用的是最新的版本。</li><li><strong>高效的资源管理</strong>：通过 Ollama，你可以有效地管理计算资源，包括 CPU 和 GPU 的使用，确保模型运行的高效性。</li></ul><h2 id="安装">安装</h2><h3 id="Linux">Linux</h3><p>使用root用户执行下面的命令：</p><pre><code class="language-shell">curl -fsSL https://ollama.com/install.sh | sh</code></pre><h3 id="windows">windows</h3><p>直接下载安装包：<a href="https://ollama.com/download/windows">windows安装包</a></p><h3 id="macos">macos</h3><p>下载压缩包：<a href="https://ollama.com/download/Ollama-darwin.zip">https://ollama.com/download/Ollama-darwin.zip</a></p><h2 id="环境变量">环境变量</h2><p>为了Ollama能够对外提供服务，需要设置OLLAMA_HOST</p><h3 id="在Linux上设置">在Linux上设置</h3><p>如果Ollama作为systemd服务运行，通过systemctl设置环境变量：</p><ol><li><p>使用systemctl edit ollama.service命令编辑systemd服务，将打开一个编辑器。</p></li><li><p>对每个环境变量，在[Service]部分添加两行行Environment：</p><pre><code>Environment="OLLAMA_HOST=0.0.0.0"Environment="OLLAMA_ORIGINS=*"</code></pre><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-20-06-image.png" alt=""></p><blockquote><p>红框内为后加的两行代码</p></blockquote></li><li><p>保存并退出。</p></li><li><p>重新加载systemd并重启Ollama：</p><pre><code>systemctl daemon-reloadsystemctl restart ollama</code></pre></li></ol><h3 id="在Windows上设置">在Windows上设置</h3><p>在Windows上，Ollama会继承您的用户和系统环境变量。</p><ol><li>首先通过任务栏图标退出Ollama，</li><li>从控制面板编辑系统环境变量，</li><li>为OLLAMA_HOST、OLLAMA_ORIGINS等编辑或新建变量。</li><li>点击OK/Apply保存，</li><li>然后从新的终端窗口运行ollama。</li></ol><h2 id="启动ollama服务">启动ollama服务</h2><p>执行命令</p><pre><code class="language-shell">systemctl start ollama</code></pre><h1 id="部署-DeepSeek">部署 DeepSeek</h1><p>通过<code>https://ollama.com/library</code>找到对应的模型，点进模型有拉取运行的命令，比如说</p><p>deepseek-r1:7b的模型，执行下面的命令就可以拉取并运行其模型：</p><pre><code class="language-shell">ollama run deepseek-r1</code></pre><p>下面是操作截图，如果你已经拉取并运行模型了，可以直接进行open-webui的步骤了</p><p>从<code>https://ollama.com/library</code>开始，页面如下：</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-06-12-image.png" alt=""></p><p>点击你需要的模型<code>deepseek-r1</code>，点击后页面如下：</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-09-27-image.png" alt=""></p><p>左侧红框是你需要下载的版本，这里默认就是7b版本了，如果你需要其他版本可以通过下拉列表切换</p><p>右侧红框就是你需要执行的命令，执行该命令就可以拉取并运行其模型</p><h1 id="open-webui">open-webui</h1><p>现在ds已经可以用了，但是还缺少应该web界面，我这边选择的是open-webui，可以直接通过浏览器访问</p><h2 id="docker-compose安装open-webui">docker-compose安装open-webui</h2><p>这个就相对简单了，我使用的docker-compose部署，下面是docker-compose文件：</p><pre><code class="language-yaml">services:  open-webui:    image: ghcr.io/open-webui/open-webui:main    container_name: open-webui    volumes:      - ./data:/app/backend/data    ports:      - "8088:8080"    restart: always</code></pre><p>不过这个启动后要等很久的时间，原因是默认的docker镜像是以openai为主的，但是默认情况下没有配，所以等的时间比较久。</p><p>出现下图中最后一行就是部署好了：</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-22-32-image.png" alt=""></p><h2 id="open-webui配置-ollama-deepseek">open-webui配置 ollama+deepseek</h2><p>看到<code>http://0.0.0.0:8080</code>这个说明可以在浏览器中打开了，注意，浏览器打开时，端口是你docker-compose里面引出的，不一定是8080，我这个docker-compose端口就是8088，接下来浏览器输入<code>http://ip:8088/</code>打开，页面如下：</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-25-59-image.png" alt=""></p><p>首次使用需要创建管理员账号，点击开始使用进行创建，页面如下：</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-27-16-image.png" alt=""></p><p>内容输入完，点击”创建管理员账号“，因为默认的openai，这一步一样的需要等很长一段时间，不要着急，慢慢等，出现下面的界面就可以进行下一步了：</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-33-15-image.png" alt=""></p><p>点击红框的按钮后，依次按照下面图片的顺序进行操作</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-34-16-image.png" alt=""></p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-37-12-image.png" alt=""></p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-37-37-image.png" alt=""></p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-46-11-image.png" alt=""></p><p>这地方注意下，1那地方的如果你不使用OpenAI API，一定要想我上面截图中那样给关了，要不之后再进页面，还是需要等很长一段时间，然后设置好ollama的连接，按照我文档中安装的话，填入的内容就是<code>http://IP:11434</code>，全部填完后保存</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-49-03-image.png" alt=""></p><p>保存后右上角出现提示，这时候就已经完全配好了，接下来，可以重新打开页面<code>http://IP:8088/</code>看看效果了</p><p>页面如下，打开后页面直接进入了，而且左上角的模型也默认加载出来了，如果你的ollama安装的多个模型，可以通过红框的下拉列表切换</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-09-50-44-image.png" alt=""></p><p>最后，就是看看提问效果了，当然，我这个服务器配置不行，速度一般般，但是提问的结果已经显示出来了</p><p><img src="https://img.huangge1199.cn/blog/ollamahe-open-webuibu-shu-ds/2025-02-27-10-34-25-image.png" alt=""></p>]]>
                    </description>
                    <pubDate>Thu, 27 Feb 2025 10:42:51 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[如何在openEuler上安装和配置openGauss数据库]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/ru-he-zai-openeuler-shang-an-zhuang-he-pei-zhi-opengauss-shu-ju-ku</link>
                    <description>
                            <![CDATA[<p>本文将详细介绍如何在openEuler 22.03 LTS SP1上安装和配置openGauss数据库，包括数据库的启动、停止、远程连接配置等关键步骤。</p><h1 id="1-安装">1、安装</h1><p>使用<a href="https://www.openeuler.org/zh/download/archive/detail/?version=openEuler%2022.03%20LTS%20SP1">OpenEuler-22.03-LTS-SP1-x64</a>版本的系统，通过命令行安装openGauss数据库。</p><h2 id="1-1-确保系统软件包索引是最新的">1.1、确保系统软件包索引是最新的</h2><p>以root权限执行以下命令：</p><pre><code class="language-shell">sudo dnf update -y</code></pre><h2 id="1-2-安装openGauss">1.2、安装openGauss</h2><p>以root权限执行以下命令：</p><pre><code class="language-shell">sudo dnf install -y opengauss</code></pre><p>安装完成后，二进制文件目录在 <code>/usr/local/opengauss</code>：</p><pre><code class="language-shell">ls -l /usr/local/opengauss</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-04-56-image.png" alt=""></p><p>默认数据目录在 <code>/var/lib/opengauss/data</code>：</p><pre><code class="language-shell">ls -l /var/lib/opengauss/data</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-06-02-image.png" alt=""></p><h1 id="2-数据库启动停止">2、数据库启动停止</h1><p>需要切换到opengauss用户下操作：</p><pre><code class="language-shell">su - opengauss</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-12-14-image.png" alt=""></p><h2 id="2-1-查询数据库状态">2.1、查询数据库状态</h2><p>在opengauss用户下执行命令：</p><pre><code class="language-shell">ps ux</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-17-06-image.png" alt=""></p><p>可以看到opengauss已经启动了</p><h2 id="2-2-停止数据库">2.2、停止数据库</h2><p>执行以下命令停止数据库：</p><pre><code class="language-shell"># 停止命令gs_ctl stop -D /var/lib/opengauss/data -Z single_node# 查看状态确认停止ps ux</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-20-11-image.png" alt=""></p><h2 id="2-3-启动数据库">2.3、启动数据库</h2><p>执行以下命令启动数据库：</p><pre><code class="language-shell"># 启动命令gs_ctl start -D /var/lib/opengauss/data -Z single_node# 查看状态确认停止ps ux</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-22-41-image.png" alt=""></p><h2 id="2-4-重启数据库">2.4、重启数据库</h2><p>执行以下命令重启数据库：</p><pre><code class="language-shell"># 重启命令gs_ctl restart -D /var/lib/opengauss/data -Z single_node# 查看状态确认停止ps ux</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-24-58-image.png" alt=""></p><h1 id="3-密码规则配置">3、密码规则配置</h1><p>建议在数据库安装好后立即配置。在<code>/var/lib/opengauss/data/postgresql.conf</code>文件的108行左右，去掉注释，设置成0或1。</p><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-37-47-image.png" alt=""></p><h1 id="4-远程连接">4、远程连接</h1><p>数据库默认安装完是不能远程连接的，需要修改配置文件。数据库的远程操作不能使用默认的超级用户，需要新建一个业务用户，而数据库的操作需要有密码，因此整个步骤如下：</p><ol><li>修改配置文件</li><li>超级用户连接数据库</li><li>给超级用户设置密码</li><li>通过超级用户创建业务用户并设置密码</li><li>给业务用户分配权限</li><li>远程连接测试</li></ol><h2 id="4-1-修改配置文件">4.1、修改配置文件</h2><ul><li>在<code>/var/lib/opengauss/data/postgresql.conf</code>文件中，设置&nbsp;<code>listen_addresses = '*'</code>（大约第68行）</li><li>在<code>/var/lib/opengauss/data/pg_hba.conf</code>文件中，设置<code>host all all 0.0.0.0/0 md5</code>（大约在91行）</li></ul><p>修改完后需要重启数据库。</p><h2 id="4-2-超级用户连接数据库">4.2、超级用户连接数据库</h2><p>以超级用户opengauss连接数据库：</p><pre><code class="language-shell">gsql -d postgres -p 7654 -r</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-28-56-image.png" alt=""></p><h2 id="4-3-给超级用户设置密码">4.3、给超级用户设置密码</h2><p>执行以下命令：</p><pre><code class="language-sql">ALTER USER opengauss WITH PASSWORD 'opengauss@123';</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-11-57-12-image.png" alt=""></p><h2 id="4-4-创建业务用户并设置密码">4.4、创建业务用户并设置密码</h2><p>执行以下命令：</p><pre><code class="language-sql">CREATE USER admin WITH PASSWORD 'admin@123';</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-12-00-03-image.png" alt=""></p><h2 id="4-5-给业务用户分配权限">4.5、给业务用户分配权限</h2><p>执行以下命令：</p><pre><code class="language-sql">GRANT ALL PRIVILEGES ON DATABASE postgres TO admin;</code></pre><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-13-06-01-image.png" alt=""></p><h2 id="4-6-远程连接测试">4.6、远程连接测试</h2><p>使用<code>Navicat Premium Lite 17</code>作为测试工具进行远程连接。</p><p><img src="https://img.huangge1199.cn/blog/setting-up-opengauss-on-openeuler/2024-09-24-13-23-39-image.png" alt=""></p><h1 id="5-总结">5、总结</h1><p>通过上述步骤，已经成功在openEuler上安装并配置了openGauss数据库，确保能够进行正常的数据库操作和远程连接。</p>]]>
                    </description>
                    <pubDate>Tue, 24 Sep 2024 17:09:59 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[你一看就懂的Git详解]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/ni-yi-kan-jiu-dong-de-git-xiang-jie</link>
                    <description>
                            <![CDATA[<h1 id="Git基础">Git基础</h1><h2 id="git简介">git简介</h2><h3 id="1-简介">1、简介</h3><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-10-28-02-image.png" alt=""></p><p>Git 是一个开源的分布式版本控制系统，用于敏捷高效地处理任何或小或大的项目。</p><p>Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。</p><p>Git 与常用的版本控制工具 CVS, Subversion 等不同，它采用了分布式版本库的方式，不必服务器端软件支持。</p><h3 id="2-Git-与-SVN-区别">2、Git&nbsp;与&nbsp;SVN&nbsp;区别</h3><ol><li>GIT 是分布式的，SVN 不是：这是 GIT 和其它非分布式的版本控制系统，例如 SVN，CVS 等，最核心的区别。</li><li>GIT 把内容按元数据方式存储，而 SVN 是按文件：所有的资源控制系统都是把文件的元信息隐藏在一个类似 .svn , .cvs 等的文件夹里。</li><li>GIT 分支和SVN的分支不同：分支在SVN中一点不特别，就是版本库中的另外的一个目录。</li><li>GIT 没有一个全局的版本号，而 SVN 有：目前为止这是跟 SVN 相比 GIT 缺少的最大的一个特征。</li><li>GIT 的内容完整性要优于 SVN：GIT 的内容存储使用的是 SHA-1哈希算法。这能确保代码内容的完整性，确保在遇到磁盘故障和网络问题时降低对版本库的破坏。</li></ol><h2 id="git安装">git安装</h2><h3 id="1-安装">1、安装</h3><p>在使用 Git 前我们需要先安装 Git。Git 目前支持 Linux/Unix、Solaris、Mac 和 Windows 平台上运行。</p><p>Git 各平台安装包下载地址为：<a href="http://git-scm.com/downloads">Git - Downloads</a></p><h3 id="2-在Linux-平台上安装">2、在Linux 平台上安装</h3><p>首先，你可以试着输入 git，看看系统有没有安装 Git：</p><pre><code class="language-shell">git</code></pre><p>显示下面的情况就是安装过git</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-10-35-57-image.png" alt=""></p><p>如果未安装，可选择直接用命令安装或者是源码安装</p><h4 id="命令安装">命令安装</h4><p>根据Linux的不同版本安装命令不同，下面是各版本的命令：</p><h5 id="Debian-Ubuntu">Debian/Ubuntu</h5><pre><code class="language-shell">apt-get install git</code></pre><h5 id="Fedora">Fedora</h5><pre><code class="language-shell"># Fedora 21 及以下yum install git# Fedora 22 及之后dnf install git</code></pre><h5 id="Gentoo">Gentoo</h5><pre><code class="language-shell">emerge --ask --verbose dev-vcs/git</code></pre><h5 id="Arch-Linux">Arch Linux</h5><pre><code class="language-shell">pacman -S git</code></pre><h5 id="openSUSE">openSUSE</h5><pre><code class="language-shell">zypper install git</code></pre><h5 id="Mageia">Mageia</h5><pre><code class="language-shell">urpmi git</code></pre><h5 id="Nix-NixOS">Nix/NixOS</h5><pre><code class="language-shell">nix-env -i git</code></pre><h5 id="FreeBSD">FreeBSD</h5><pre><code class="language-shell">pkg install git</code></pre><h5 id="Solaris-9-10-11--OpenCSW-">Solaris 9/10/11 (<a href="https://www.opencsw.org/">OpenCSW</a>)</h5><pre><code class="language-shell">pkgutil -i git</code></pre><h5 id="Solaris-11-Express">Solaris 11 Express</h5><pre><code class="language-shell">pkg install developer/versioning/git</code></pre><h5 id="OpenBSD">OpenBSD</h5><pre><code class="language-shell">pkg_add git</code></pre><h5 id="Alpine">Alpine</h5><pre><code class="language-shell">apk add git</code></pre><h4 id="源码安装">源码安装</h4><p>我们来学习在 Linux 中如何使用源代码安装 Git, 有些 Linux 版本自带的安装包更新起来并不及时，那么从源代码安装其实该算是最佳选择。</p><p>Git 的工作需要调用 curl，zlib，openssl，expat，libiconv 等库的代码，所以需要先安装这些依赖工具。</p><p>在有 yum 的系统上（比如 Fedora）可以用下面的命令安装：</p><pre><code class="language-shell">yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel</code></pre><p>在有 apt-get 的系统上（比如 Debian 体系）可以用下面的命令安装：</p><pre><code class="language-shell">$ apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev</code></pre><p>之后，从下面的 Git 官方站点下载最新版本源代码：<a href="http://git-scm.com/download">http://git-scm.com/download</a></p><p>然后编译并安装：</p><pre><code class="language-shell"># 解压tar -zxf git-1.7.2.2.tar.gz# 切换目录cd git-1.7.2.2# 设置变量make prefix=/usr/local all# 安装sudo make prefix=/usr/local install</code></pre><h3 id="3-在-Windows-平台上安装">3、在 Windows 平台上安装</h3><p>在 Windows 平台上安装 Git 同样轻松，直接下载exe安装包即可。</p><p>安装包下载地址：<a href="https://git-scm.com/download/win">Git - Downloading Package</a></p><h3 id="4-在-Mac-平台上安装">4、在 Mac 平台上安装</h3><p>既可以通过命令安装也可以通过安装器安装</p><h3 id="Homebrew">Homebrew</h3><p>如果有它，可以直接使用下面的命令安装：</p><pre><code class="language-shell">brew install git</code></pre><h3 id="MacPorts">MacPorts</h3><p>如果有它，可以直接使用下面的命令安装：</p><pre><code class="language-shell">sudo port install git</code></pre><h3 id="安装器">安装器</h3><p>可以使用图形化 Git 安装工具</p><p>下载地址为：<a href="http://sourceforge.net/projects/git-osx-installer/">git-osx-installer (abandoned) download | SourceForge.net</a></p><h2 id="Git-工作流程">Git 工作流程</h2><p>Git 的一般工作流程如下：</p><ul><li>克隆 Git 资源作为工作目录。</li><li>在克隆的资源上添加或修改文件。</li><li>如果其他人修改了，你可以更新资源。</li><li>在提交前查看修改。</li><li>提交修改。</li><li>在修改完成后，如果发现错误，可以撤回提交并再次修改并提交。</li></ul><p><strong>下图展示了 Git 的工作流程：</strong></p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-11-36-00-image.png" alt=""></p><p>下面来理解下Git 工作区、暂存区和版本库概念</p><ul><li>工作区：就是你在电脑里能看到的目录。</li><li>暂存区：英文叫 stage, 或 index。一般存放在 ".git目录下" 下的 index 文件（.git/index）中，所以我们把暂存区有时也叫作索引（index）。</li><li>版本库：工作区有一个隐藏目录 .git，这个不算工作区，而是 Git 的版本库。</li></ul><h1 id="Git基本操作">Git基本操作</h1><h2 id="创建版本库---git-init--">创建版本库&nbsp;(&nbsp;git&nbsp;init&nbsp;)</h2><h3 id="描述">描述</h3><p>用 git init 在目录中创建新的 Git 仓库。 你可以在任何时候、任何目录中这么做，完全是本地化的。</p><h3 id="语法">语法</h3><pre><code class="language-shell">git init</code></pre><h3 id="例子">例子</h3><p>比如我们创建 gitlearn 项目：</p><pre><code class="language-shell"># 创建项目目录mkdir gitlearn# 切换目录cd gitlearn# 创建版本库git init</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-11-55-28-image.png" alt=""></p><h2 id="添加到暂存区---git-add--">添加到暂存区&nbsp;(&nbsp;git&nbsp;add&nbsp;)</h2><h3 id="描述-">描述</h3><p>当我们初始化项目后, 在工作区里进行增加, 修改, 删除 文件操作.</p><p>然后可以通过&nbsp;git add&nbsp;将文件添加到暂存区，作为下次提交的(部分或全部)内容。</p><h3 id="语法-">语法</h3><pre><code class="language-shell">git add &lt;文件/文件夹&gt; [&lt;args&gt;]</code></pre><p><strong>git add .</strong>&nbsp;：他会监控工作区的状态树，使用它会把工作时的所有变化提交到暂存区，包括文件内容修改(modified)以及新文件(new)，但不包括被删除的文件。</p><p><strong>git add -u</strong>&nbsp;：他仅监控已经被add的文件（即tracked file），他会将被修改的文件提交到暂存区。add -u 不会提交新文件（untracked file）。（git add --update的缩写）</p><p><strong>git add -A</strong>&nbsp;：是上面两个功能的合集, 也就是说包括删除的文件也会被提交（git add --all的缩写）</p><h3 id="例子-">例子</h3><p>我们在 gitlearn 项目添加两个文件并添加到暂存区</p><pre><code class="language-shell"># 创建空文件READMEtouch README# 创建空文件hello.phptouch hello.php# 查看当前目录ls -al# 查看项目的当前状态git status -s# 将README和hello.php添加到暂存区git add README hello.php# 查看项目的当前状态git status -s# 在 README 中添加内容echo "Git 测试" &gt;&gt; README# 查看项目的当前状态git status -s将目录下的所有内容添加到暂存区git add .# 查看项目的当前状态git status -s</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-13-09-50-image.png" alt=""></p><p>"A" 状态的意思是，这个文件已经添加到缓存中</p><p>"AM" 状态的意思是，这个文件在我们将它添加到缓存之后又有改动</p><h2 id="查看状态---git-status--">查看状态&nbsp;(&nbsp;git&nbsp;status&nbsp;)</h2><h3 id="描述--">描述</h3><p><strong>git&nbsp;status</strong>&nbsp;查看本地工作区、暂存区中文件的修改状态</p><h3 id="语法--">语法</h3><pre><code class="language-shell">git status</code></pre><h3 id="例子--">例子</h3><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-13-19-48-image.png" alt=""></p><h2 id="查看改动---git-diff--">查看改动&nbsp;(&nbsp;git&nbsp;diff&nbsp;)</h2><h3 id="描述---">描述</h3><p>执行&nbsp;<strong>git diff</strong>&nbsp;来查看执行&nbsp;<strong>git status</strong>&nbsp;的结果的详细信息。</p><p>git diff 命令显示已写入缓存与已修改但尚未写入缓存的改动的区别。</p><h3 id="语法---">语法</h3><pre><code class="language-shell"># 尚未缓存的改动git diff# 查看已缓存的改动git diff --cached# 查看已缓存的与未缓存的所有改动git diff HEAD# 显示摘要而非整个 diffgit diff --stat</code></pre><h3 id="例子---">例子</h3><pre><code class="language-shell"># 向hello.php中添加内容echo -e "&lt;?php\necho '查看改动test';"&gt;&gt;hello.php# 查看hello.php文件内容cat hello.php# 查看项目的当前状态git status -s# 查看尚未缓存的改动git diff# 将hello.php的改动添加到暂存区git add hello.php# 查看项目的当前状态git status -s# 查看已缓存的改动git diff --cached</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-13-36-34-image.png" alt=""></p><h2 id="向仓库提交代码---git-commit--">向仓库提交代码&nbsp;(&nbsp;git&nbsp;commit&nbsp;)</h2><h3 id="描述----">描述</h3><p>使用&nbsp;<strong>git add</strong>&nbsp;命令将想要快照的内容写入缓存区， 而执行&nbsp;<strong>git commit</strong>&nbsp;将缓存区内容添加到仓库中。</p><p>如果你觉得&nbsp;<strong>git add</strong>&nbsp;提交缓存的流程太过繁琐，Git 也允许你用 -a 选项跳过这一步。</p><p>Git 为你的每一个提交都记录你的名字与电子邮箱地址，所以第一步需要配置用户名和邮箱地址。</p><pre><code class="language-shell"># 配置全局的名称git config --global user.name 'huangge1199'# 配置全局的邮箱git config --global user.email huangge1199@hotmail.com</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-13-44-45-image.png" alt=""></p><h3 id="语法----">语法</h3><pre><code class="language-shell"># 将想要快照的内容写入缓存区git add # 将缓存区内容添加到仓库git commit# 将想要快照的内容添加到仓库# 注：和前面的两条命令效果相同git commit -a</code></pre><h3 id="例子一">例子一</h3><pre><code class="language-shell"># 我们使用 -m 选项以在命令行中提供提交注释git commit -m '初始化项目'git status</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-13-58-11-image.png" alt=""></p><p>以上输出说明我们在最近一次提交之后，没有做任何改动，是一个"working directory clean：干净的工作目录"。</p><blockquote><p>如果你没有设置 -m 选项，Git 会尝试为你打开一个编辑器以填写提交信息。 如果 Git 在你对它的配置中找不到相关信息，默认(Linux)会打开 vim。</p></blockquote><h3 id="例子二">例子二</h3><p>如果你觉得&nbsp;<strong>git add</strong>&nbsp;提交缓存的流程太过繁琐，Git 也允许你用 -a 选项跳过这一步。</p><pre><code class="language-shell">echo -e "echo '向仓库提交代码测试：git commit -a';"&gt;&gt;hello.phpgit statusgit commit -am '修改hello'git status</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-14-03-00-image.png" alt=""></p><h2 id="取消已缓存的内容---git-reset-HEAD--">取消已缓存的内容&nbsp;(&nbsp;git&nbsp;reset&nbsp;HEAD&nbsp;)</h2><h3 id="语法-----">语法</h3><pre><code class="language-shell">git reset HEAD -- &lt;文件/文件夹&gt;</code></pre><h3 id="例子----">例子</h3><pre><code class="language-shell">echo -e "echo '取消缓存测试';"&gt;&gt;hello.phpecho -e "取消缓存测试"&gt;&gt;READMEgit status -sgit add .git status -sgit reset HEAD -- hello.phpgit status -sgit commit -m '修改README'git status -sgit commit -am '修改 hello 文件'git status</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-14-16-33-image.png" alt=""></p><h2 id="删除文件---git-rm--">删除文件&nbsp;(&nbsp;git&nbsp;rm&nbsp;)</h2><h3 id="语法------">语法</h3><pre><code class="language-shell"># git rm 从版本库中删除文件git rm &lt;文件&gt;# 如果删除之前修改过并且已经放到暂存区域的话，则必须要用强制删除选项 -fgit rm -f &lt;文件&gt;# 如果把文件从暂存区域移除，但仍然希望保留在当前工作目录中，换句话说，仅是从跟踪清单中删除，使用 --cached 选项即可git rm --cached &lt;文件&gt;# 用版本库里的版本替换工作区的版本，无论工作区是修改还是删除，都可以“一键还原”git checkout -- &lt;文件&gt;# 递归删除目录下的所有文件和子目录git rm –r &lt;文件夹&gt;</code></pre><h3 id="例子-从版本库中和工作区中删除">例子：从版本库中和工作区中删除</h3><pre><code class="language-shell">echo "# 这是一个测试文件的测试" &gt; gittest.mdgit add .git commit -m "添加测试文件"git rm gittest.mdls -al</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-14-41-43-image.png" alt=""></p><h3 id="例子-删除暂存区或分支上的文件--不删除本地">例子：删除暂存区或分支上的文件, 不删除本地</h3><pre><code class="language-shell">git rm --cached READMEls -al</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-14-50-08-image.png" alt=""></p><h2 id="移动-重命名---git-mv--">移动/重命名&nbsp;(&nbsp;git&nbsp;mv&nbsp;)</h2><h3 id="描述-----">描述</h3><p>git mv 命令用于移动或重命名一个文件、目录、软连接。</p><h3 id="语法-------">语法</h3><pre><code class="language-shell">git mv 原文件名 新文件名</code></pre><h3 id="例子-----">例子</h3><pre><code class="language-shell">git add READMEgit mv README  README.mdls -al</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-15-28-12-image.png" alt=""></p><h1 id="远程仓库">远程仓库</h1><h2 id="添加远程仓库">添加远程仓库</h2><p>我这边使用的是自建的gitea</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-15-36-18-image.png" alt=""></p><p>创建仓库后出现下面的页面</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-15-37-24-image.png" alt=""></p><p>现在,这个仓库还是空的, gitea告诉我们，可以从这个仓库克隆出新的仓库，也可以把一个已有的本地仓库与之关联，然后，把本地仓库的内容推送到 gitea仓库。</p><p>我们将本地已有仓库与之关联:</p><pre><code class="language-shell">git remote add origin https://gitea.huangge1199.cn/huangge1199/gitlearn.git</code></pre><p>然后,我们把本地库的所有内容推送到远程库上:</p><pre><code class="language-shell">git push -u origin master</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-15-41-28-image.png" alt=""></p><p>由于远程库是空的，我们第一次推送<code>master</code>分支时，加上了<code>-u</code>参数</p><p>另外红框的地方需要输入gitea的用户名和密码</p><p>推送成功后, 就可以在 gitea页面看到内容了:</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-15-44-06-image.png" alt=""></p><h2 id="克隆操作---git-clone--">克隆操作&nbsp;(&nbsp;git&nbsp;clone&nbsp;)</h2><p>使用&nbsp;<strong>git clone</strong>&nbsp;拷贝一个 Git 仓库到本地，让自己能够查看该项目，或者进行修改。</p><p>如果你需要与他人合作一个项目，或者想要复制一个项目，看看代码，你就可以克隆那个项目。 执行命令：</p><pre><code class="language-shell"> git clone [url]</code></pre><p>[url] 为你想要复制的项目地址。</p><p>例如,我们在 gitea 创建一个新的 gitClone</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-15-49-08-image.png" alt=""></p><p>填上仓库名称，勾选上”初始化仓库(添加. gitignore、许可证和自述文件)“</p><p>创建完毕后可以看到仓库中有一个 README.md 文件:</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-15-50-28-image.png" alt=""></p><p>现在,将以上项目克隆到本地：</p><pre><code class="language-shell">git clone https://gitea.huangge1199.cn/huangge1199/gitClone.gitcd gitClonels -al</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-22-15-52-52-image.png" alt=""></p><blockquote><p>默认情况下，Git 会按照你提供的 URL 所指示的项目的名称创建你的本地项目目录。 通常就是该 URL 最后一个 / 之后的项目名称。如果你想要一个不一样的名字， 你可以在该命令后加上你想要的名称。</p></blockquote><blockquote><p>例如: git&nbsp;clone&nbsp;<a href="https://gitea.huangge1199.cn/huangge1199/gitClone.git">https://gitea.huangge1199.cn/huangge1199/gitClone.git</a> app</p></blockquote><h2 id="更新数据">更新数据</h2><h3 id="更新数据---git-fetch--">更新数据&nbsp;(&nbsp;git fetch )</h3><p><strong>git&nbsp;fetch</strong>：从远程获取最新版本到本地，不会自动 merge</p><pre><code class="language-shell">git checkout issue12git fetch origin issue12git log -p issue12..origin/issue12git merge origin/issue12</code></pre><blockquote><p>解析:</p><p>(1).切换到issue12分支</p><p>(2).从远程的origin的issue12主分支下载最新的版本到origin/issue12分支上</p><p>(3).比较本地的issue12分支和origin/issue12分支的差别&nbsp; ( git&nbsp;log 常用&nbsp;-p&nbsp;选项展开显示每次提交的内容差异 )</p><p>(4).将origin/issue12分支合并到issue12</p></blockquote><h3 id="更新数据---git-pull---">更新数据&nbsp;(&nbsp;git&nbsp;pull&nbsp;&nbsp;)</h3><p><strong>git&nbsp;pull</strong>：相当于是从远程获取最新版本并merge到本地</p><pre><code class="language-shell">git checkout issue13git pull origin issue13</code></pre><blockquote><p>上述 git pull 命令,&nbsp; 相当于 git fetch 和 git merge</p></blockquote><h1 id="Git分支管理">Git分支管理</h1><h2 id="Git-分支原理">Git 分支原理</h2><p>Git 每次提交的版本, Git 都将其串成一条时间线, 这条时间线就是一个分支, 当你执行&nbsp;<strong>git init</strong>&nbsp;的时候，缺省情况下 Git 就会为你创建 "master" 分支。<code>HEAD</code>严格来说不是指向提交，而是指向<code>master</code>，<code>master</code>才是指向提交的，所以，<code>HEAD</code>指向的就是当前分支。</p><p>如图所示:</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-09-00-41-image.png" alt=""></p><p>每次提交，<code>master</code>分支都会向前移动一步，这样，随着你不断提交，<code>master</code>分支的线也越来越长.</p><p>当我们创建新的分支，如<code>dev</code>时，Git 新建了一个指针叫<code>dev</code>，指向与<code>master</code>相同的提交，再把<code>HEAD</code>指向<code>dev</code>，现在表示当前分支在<code>dev</code>上</p><p>如图所示:</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-09-02-17-image.png" alt=""></p><p>从现在开始, 对工作区的修改和提交就是针对<code>dev</code>分支了，比如新提交一次后，<code>dev</code>指针往前移动一步，而<code>master</code>指针不变.</p><p>如图所示:</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-09-04-07-image.png" alt=""></p><p>我们在<code>dev</code>上完成相应的开发后, 我们将其合并到 master 分支上, 切换回 master 分支.</p><p>如图所示:</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-09-05-48-image.png" alt=""></p><p>合并完成后, 也可以删除 dev 分支, 原理是将 dev 指针删除</p><p>如图所示:</p><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-09-06-25-image.png" alt=""></p><h2 id="分支基础命令">分支基础命令</h2><p>几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来，然后在不影响主线的同时继续工作。</p><p>有人把&nbsp;<strong>Git</strong>&nbsp;的分支模型称为"必杀技特性"，而正是因为它，将 Git 从版本控制系统家族里区分出来。</p><p>创建分支命令：</p><pre><code class="language-shell">git branch 分支名</code></pre><p>切换分支命令:</p><pre><code class="language-shell">git checkout 分支名</code></pre><p>当你切换分支的时候，Git 会用该分支的最后提交的快照替换你的工作目录的内容， 所以多个分支不需要多个目录。</p><p>合并分支命令:</p><pre><code class="language-shell">git merge </code></pre><p>你可以多次合并到统一分支， 也可以选择在合并之后直接删除被并入的分支。</p><h2 id="查看-创建及切换分支">查看、创建及切换分支</h2><h3 id="语法--------">语法</h3><pre><code class="language-shell"># 列出分支git branch# 创建分支git branch 分支名# 切换分支git checkout 分支名# 创建新分支并立即切换到该分支git checkout -b 分支名</code></pre><h3 id="例子一-">例子一</h3><pre><code class="language-shell"># 列出分支git branch# 创建分支devgit branch dev# 列出分支git branch# 列出当前目录的所有文件ls -al# 切换到dev分支git checkout dev# 创建新文件test.mdecho '# Git分支学习测试' &gt; test.md# 添加到暂存区git add .# 提交到dev分支git commit -m "新增test.md文件"# 列出当前目录的所有文件ls -al# 切换回master分支git checkout master# 列出当前目录的所有文件ls -al</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-09-21-12-image.png" alt=""></p><h3 id="例子二-">例子二</h3><pre><code class="language-shell"># 创建新分支newtest并切换到新分支git checkout -b newtest# 查看所有分支git branch# 删除hello.php文件git rm hello.php# 列出目录下所有文件ls -al# 提交变更到newtest分支git commit -am "removed hello.php"# 切换回master分支git checkout master# 列出目录下所有文件ls -al</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-09-31-46-image.png" alt=""></p><h2 id="分支合并">分支合并</h2><h3 id="描述------">描述</h3><p>一旦某分支有了独立内容，你终究会希望将它合并回到你的主分支。</p><h3 id="语法---------">语法</h3><pre><code class="language-shell">git merge 分支名</code></pre><h3 id="例子------">例子</h3><pre><code class="language-shell"># 创建新分支dev2并切换到新分支git checkout -b dev2# 创建空文件dev2.mdtouch dev2.md# 添加到暂存区git add .# 提交到dev2分支git commit -m "新增dev2.md"# 切回master分支git checkout master# 列出目录下所有文件ls -al# 将dev2分支合并到当前master分支git merge dev2# 列出目录下所有文件ls -al</code></pre><h2 id="删除分支">删除分支</h2><h3 id="语法----------">语法</h3><pre><code class="language-shell">git branch -d &lt;分支名&gt;</code></pre><h3 id="例子-------">例子</h3><pre><code class="language-shell"># 查看所有分支git branch# 删除分支dev2git branch -d dev2# 查看所有分支git branch</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-10-17-52-image.png" alt=""></p><h2 id="解决冲突">解决冲突</h2><p>我们先创造一个冲突出来, 比如, 在两个分支中修改了同一个文件的同一行代码,在合并的时候就会发生冲突，出现冲突后我们再解决冲突。下面就是创建冲突、解决冲突的全过程。</p><pre><code class="language-shell"># 查看所有分支git branch# 查看当前目录ls -al# 查看gittest.md文件内容cat gittest.md# 在gittest.md文件结尾添加“冲突测试”echo "冲突测试" &gt;&gt; gittest.md# 查看gittest.md文件内容cat gittest.md# 查看git状态git status -s# 提交变化到master分支git commit -am "冲突测试"# 创建并切换到dev1分支git checkout -b dev1# 查看当前目录ls -al# 查看gittest.md文件内容cat gittest.md# 将gittest.md文件最后一行替换成“冲突测试dev1”sed -i '$s/.*/冲突测试dev1/' gittest.md# 查看gittest.md文件内容cat gittest.md# 提交变化到dev1分支git commit -am "冲突测试dev1"# 切换回master分支git checkout master# 将gittest.md文件最后一行替换成“冲突测试master”sed -i '$s/.*/冲突测试master/' gittest.md# 查看gittest.md文件内容cat gittest.md# 提交变化到master分支git commit -am "冲突测试master"# 合并dev1分支到mastergit merge dev1# 查看git状态git status# 查看gittest.md文件内容cat gittest.md# 将gittest.md文件中冲突部分替换成“冲突测试master 和 冲突测试dev1”sed -i '/^&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD$/,/^&gt;&gt;&gt;&gt;&gt;&gt;&gt; dev1$/c\冲突测试master 和 冲突测试dev1' gittest.md# 查看gittest.md文件内容cat gittest.md# 提交冲突修复内容到master分支git commit -am "修复冲突"# 查看分支历史git log --graph --pretty=oneline --abbrev-commit</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-01-46-image.png" alt=""></p><h2 id="分支管理策略">分支管理策略</h2><p>通常 Git 在合并分支的时候会用 Fast forward 模式, 这样删除分支后, 也会丢掉分支信息.</p><p>我们在合并分支时可以使用&nbsp;--no-ff&nbsp;参数,强制禁用 Fastforward 模式, 这样 Git 在合并时会生成一个新的 commit(提交), 然后我们就可以在历史分支上看出分支信息.</p><p>我们来操作下看看效果, 我们创建一个 dev2 的分支</p><pre><code class="language-shell">git checkout -b dev2</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-18-23-image.png" alt=""></p><p>修改 gittest.md 文件, 然后提交修改:</p><pre><code class="language-shell">ls -alcat gittest.mdecho "分支策略。。。测试" &gt;&gt; gittest.mdcat gittest.mdgit commit -am "分支策略"</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-22-32-image.png" alt=""></p><p>切回 master 分支, 然后使用 git merge --no-ff 合并分支:</p><pre><code class="language-shell">git checkout mastergit merge --no-ff -m "merge with no-ff" dev2</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-24-12-image.png" alt=""></p><p>因为我们禁用 Fast forward 模式, 所以本次合并会创建一个新的 commit(提交), 我们用 -m 给这次 commit 加上描述</p><p>然后我们用 git log 查看下分支历史</p><pre><code class="language-shell">git log --graph --pretty=oneline --abbrev-commit</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-25-13-image.png" alt=""></p><h2 id="多人协作">多人协作</h2><h3 id="查看远程库信息">查看远程库信息</h3><p>当我们从远程仓库克隆时，实际上 Git 自动把本地的<code>master</code>分支和远程的<code>master</code>分支对应起来了(远程仓库默认名字是 origin)</p><p>可以用 git remote 查看远程库的信息</p><pre><code class="language-shell">git remote</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-28-41-image.png" alt=""></p><p>加参数 -v 可以显示更详细的信息：</p><pre><code class="language-shell">git remote -v</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-29-25-image.png" alt=""></p><blockquote><p>上面显示了可以抓取和推送的<code>origin</code>的地址。如果没有推送权限，就看不到push的地址。</p></blockquote><h3 id="推送分支">推送分支</h3><p>我们想将本地分支推送到远程库,可以使用 git push 命令,在推送时,要指定本地分支, 这样，Git 就会把该分支推送到远程库对应的远程分支上：</p><pre><code class="language-shell">git push origin master</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-53-32-image.png" alt=""></p><p>如果要推送其他分支, 比如 dev2 :</p><pre><code class="language-shell">git push origin dev2</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-11-54-27-image.png" alt=""></p><h1 id="回退与撤销">回退与撤销</h1><h2 id="撤销修改---git-checkout----file-">撤销修改&nbsp;(&nbsp;git&nbsp;checkout -- file)</h2><h3 id="描述-------">描述</h3><p>把&nbsp;该文件&nbsp;在工作区的修改全部撤销</p><h3 id="语法-----------">语法</h3><pre><code class="language-shell">git checkout -- &lt;file&gt;</code></pre><p>如果 文件 修改后还没有被放到暂存区，现在，撤销修改就是用版本库的版本覆盖当前的文件。</p><p>如果 文件 已经添加到暂存区后，又作了修改，现在，撤销修改就是将暂存区中的文件版本覆盖当前的文件。</p><blockquote><p>总之，就是让这个文件回到最近一次<code>git commit</code>或<code>git add</code>时的状态。</p></blockquote><h3 id="例子--------">例子</h3><pre><code class="language-shell"># 查看git状态git status# 查看gittest.md文件内容cat gittest.md# 修改gittest.md文件echo "撤销修改。。。测试" &gt;&gt; gittest.md# 查看gittest.md文件内容cat gittest.md# 查看本地与暂存区的不同git diff# 撤销本地修改git checkout -- gittest.md# 查看gittest.md文件内容cat gittest.md</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-13-27-33-image.png" alt=""></p><h2 id="历史记录---git-log--">历史记录&nbsp;(&nbsp;git&nbsp;log&nbsp;)</h2><p>在实际工作中，我们脑子里怎么可能记得一个几千行的文件每次都改了什么内容，不然要版本控制系统干什么。</p><p>Git 可以使用&nbsp;<strong>git log</strong>&nbsp;命令告诉我们历史记录：</p><pre><code class="language-shell">git log</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-13-37-09-image.png" alt=""></p><p><code>git log</code>命令显示从最近到最远的提交日志</p><p>如果嫌 git log 输出信息太多，看得眼花缭乱，可以加上<code>--pretty=oneline</code>参数:</p><pre><code class="language-shell">git log --pretty=oneline</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-13-38-36-image.png" alt=""></p><h2 id="版本回退---git-reset--">版本回退&nbsp;(&nbsp;git&nbsp;reset&nbsp;)</h2><h3 id="描述--------">描述</h3><p>要把当前版本回退到上一个版本，可以使用<code>git reset</code>命令.</p><h3 id="语法------------">语法</h3><p>回退到上一个版本：</p><pre><code class="language-shell">git reset --hard HEAD^</code></pre><blockquote><p>在 Git 中，用 HEAD 表示当前版本，上一个版本就是 HEAD^，上上一个版本就是 HEAD^^，当然往上100个版本写100个^比较容易数不过来，所以写成HEAD~100</p></blockquote><p>回退到指定&nbsp;commit id 版本：</p><pre><code class="language-shell">git reset --hard &lt;commit id&gt;</code></pre><h3 id="例子---------">例子</h3><pre><code class="language-shell"># 查看当前历史记录git log --pretty=oneline# 回退到上一个版本git reset --hard HEAD^# 查看当前历史记录git log --pretty=oneline# 显示整个本地仓储的 commitgit reflog# 回退失误,通过回退到指定 commit id 版本，改回原来的版本git reset --hard cd531a8# 查看当前历史记录git log --pretty=oneline</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-13-54-38-image.png" alt=""></p><h1 id="标签管理">标签管理</h1><h2 id="创建标签">创建标签</h2><h3 id="语法-------------">语法</h3><pre><code class="language-shell"># 创建一个新标签，默认打在最新提交的 commit id 上的。git tag &lt;标签名&gt;# 在指定commit id上打标签git tag &lt;标签名&gt; &lt;commit id&gt;# 创建带有说明的标签git tag -a &lt;标签名&gt; -m &lt;说明&gt; &lt;commit id&gt;</code></pre><h3 id="例子----------">例子</h3><pre><code class="language-shell">git log --pretty=oneline --abbrev-commitgit tag v1.0git log --pretty=oneline --abbrev-commitgit tag v0.8 b2db307git tag -a v0.9 -m "标签说明" 0d5375b</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-06-23-image.png" alt=""></p><h2 id="查看标签">查看标签</h2><h3 id="语法--------------">语法</h3><pre><code class="language-shell"># 查看所有标签git tag# 查看标签信息git show &lt;标签名&gt;</code></pre><h3 id="例子-----------">例子</h3><pre><code class="language-shell">git taggit show v0.9</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-12-35-image.png" alt=""></p><h2 id="删除和推送标签">删除和推送标签</h2><p>可以使用 git tag -d 标签名 删除打错的标签：</p><pre><code class="language-shell">git tag -d v0.8git tag</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-15-54-image.png" alt=""></p><p>到目前为止,我们打的标签都只是存储在本地</p><p>如果要推送某个标签到远程，使用命令&nbsp;git push origin 标签名</p><pre><code class="language-shell">git push origin v1.0</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-31-13-image.png" alt=""></p><p>也可以，一次性推送全部尚未推送到远程的本地标签：</p><pre><code class="language-shell">git push origin --tags</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-32-13-image.png" alt=""></p><p>如果标签已经推送到远程，要删除远程标签就麻烦一点，先从本地删除，然后，从远程删除。删除命令也是push</p><pre><code class="language-shell">git tag -d v0.9git push origin :refs/tags/v0.9</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-33-46-image.png" alt=""></p><h1 id="自定义Git">自定义Git</h1><h2 id="Git-配置">Git&nbsp;配置</h2><p>Git 提供了一个叫做 git config 的工具，专门用来配置或读取相应的工作环境变量。</p><p>这些环境变量，决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方：</p><ul><li>/etc/gitconfig&nbsp;文件：系统中对所有用户都普遍适用的配置。若使用&nbsp;git config&nbsp;时用&nbsp;--system&nbsp;选项，读写的就是这个文件。</li><li>~/.gitconfig&nbsp;文件：用户目录下的配置文件只适用于该用户。若使用&nbsp;git config&nbsp;时用&nbsp;--global&nbsp;选项，读写的就是这个文件。</li><li>当前项目的 Git 目录中的配置文件（也就是工作目录中的&nbsp;.git/config&nbsp;文件）：这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置，所以&nbsp;.git/config&nbsp;里的配置会覆盖&nbsp;/etc/gitconfig&nbsp;中的同名变量。</li></ul><p>在 Windows 系统上，Git 会找寻用户主目录下的 .gitconfig 文件。主目录即 $HOME 变量指定的目录，一般都是 C:\Documents and Settings$USER。</p><p>此外，Git 还会尝试找寻 /etc/gitconfig 文件，只不过看当初 Git 装在什么目录，就以此作为根目录来定位。</p><h3 id="配置用户信息">配置用户信息</h3><p>配置个人的用户名称和电子邮件地址：</p><pre><code class="language-shell">git config --global user.name "huangge1199"git config --global user.email huangge1199@hotmail.com</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-38-54-image.png" alt=""></p><h3 id="查看配置信息">查看配置信息</h3><p>要检查已有的配置信息，可以使用&nbsp;<strong>git config --list</strong>&nbsp;命令：</p><pre><code class="language-shell">git config --list</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-39-44-image.png" alt=""></p><p>有时候会看到重复的变量名，那就说明它们来自不同的配置文件（比如 /etc/gitconfig 和 ~/.gitconfig），不过最终 Git 实际采用的是最后一个。</p><p>这些配置我们也可以在&nbsp;~/.gitconfig&nbsp;或&nbsp;/etc/gitconfig&nbsp;看到，如下所示：</p><pre><code class="language-shell">cat ~/.gitconfig</code></pre><p><img src="https://img.huangge1199.cn/blog/gitStudy/2024-07-23-14-41-21-image.png" alt=""></p><h2 id="忽略特殊文件">忽略特殊文件</h2><p>有时你必须把某些文件放入 Git 工作目录中, 又不能将其提交, 比如:存储了数据库密码的配置文件.</p><p>我们只需在 Git 工作区的根目录下创建一个名为 .gitignore 的文件, 写入过滤规则就行了.</p><p>忽略文件的原则是：</p><ol><li>忽略操作系统自动生成的文件，比如缩略图等；</li><li>忽略编译生成的中间文件、可执行文件等，也就是如果一个文件是通过另一个文件自动生成的，那自动生成的文件就没必要放进版本库，比如 Java 编译产生的 .class 文件；</li><li>忽略你自己的带有敏感信息的配置文件，比如存放口令的配置文件。</li></ol><p>例如, 这是 java 项目的 .gitignore 文件</p><pre><code class="language-gitignore"># Compiled class file*.class# Log file*.log# BlueJ files*.ctxt# Mobile Tools for Java (J2ME).mtj.tmp/# Package Files #*.jar*.war*.ear*.zip*.tar.gz*.rar# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xmlhs_err_pid*</code></pre>]]>
                    </description>
                    <pubDate>Tue, 23 Jul 2024 18:03:11 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[element-plus选择器自定义筛选方法（拼音首字母搜索）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/element-plus-xuan-ze-qi-zi-ding-yi-shai-xuan-fang-fa--pin-yin-shou-zi-mu-sou-suo-</link>
                    <description>
                            <![CDATA[<h1 id="引言">引言</h1><p>最近，来了个需求，需要在下拉列表中做筛选。下拉列表显示的是中文，但筛选时可能会输入中文的拼音首字母。因此，需要实现一个筛选功能，能够根据拼音首字母筛选出匹配的选项。</p><h1 id="自定义筛选方法">自定义筛选方法</h1><p>前端使用的是 <a href="https://cn.vuejs.org/guide/introduction.html">vue3</a> 和 <a href="https://element-plus.org/zh-CN/component/overview.html">element-plus</a>。我使用的组件是 <a href="https://element-plus.org/zh-CN/component/select.html">Select 选择器 | Element Plus</a> 。为 <code>el-select</code> 添加 <code>filterable</code> 属性即可启用搜索功能。默认情况下，Select 会找出所有 <code>label</code> 属性包含输入值的选项。但这里需要匹配拼音首字母进行搜索，因此要通过传入一个 <code>filter-method</code> 来实现。<code>filter-method</code> 是一个函数，它会在输入值发生变化时调用，参数为当前输入值。</p><p>下面是这部分的简短代码：</p><p>vue部分：</p><pre><code class="language-vue">&lt;el-select  v-model="queryParams.word"  filterable  placeholder="请输入"  clearable  :filter-method="filterMethod"  &gt;  &lt;el-option    v-for="word in words"    :key="word"    :label="word"    :value="word"  /&gt;&lt;/el-select&gt;/el-form-item&gt;</code></pre><p>JS部分：</p><pre><code class="language-js">const showSearch = ref(true);// option选项const words = ref(['你好', '世界', '中国', '中国最棒'])// 保留原始的option选项const wordsOld = ref(['你好', '世界', '中国', '中国最棒'])const data = reactive({  queryParams: {    word: null,  },});const { queryParams } = toRefs(data);// 多选框选中数据function filterMethod(val){  // 如果有输入值，根据输入内容进行筛选  if(val){    // 先将option中的选项words清空    words.value = []    // 从原始的option选项中遍历筛选    wordsOld.value.forEach(word =&gt; {      // 添加筛选逻辑，满足的word添加到words中显示      words.value.push(word)    })  } else {    // 如果没有输入值，恢复成原始的option选项    words.value = wordsOld.value  }}</code></pre><h1 id="拼音首字母匹配">拼音首字母匹配</h1><p>可以使用 <code>pinyin-pro</code> 来实现拼音匹配，例子如下：</p><pre><code class="language-js">import { pinyin } from 'pinyin-pro';const word = ref('中国最棒')const firstLetterPinyin = pinyin(word, { pattern: 'first' }).replace(/\s+/g, '');if(firstLetterPinyin.includes(queryLower)){  words.value.push(word)}</code></pre><p><code>pinyin</code> 方法使用 <code>pinyin-pro</code> 库将汉字转换为指定样式的拼音字符串。这里面'first'样式为获取每一个字的小写拼音首字母，中间用空格分割，而我匹配时不需要空格，因此在<code>firstLetterPinyin</code>设值时添加了<code>replace(/\s+/g, '')</code>将转换的字符串去除空格。</p><h1 id="完整的vue代码">完整的vue代码</h1><pre><code class="language-vue">&lt;template&gt;  &lt;div class="app-container"&gt;    &lt;el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"&gt;      &lt;el-form-item label="中文拼音匹配选择器"&gt;        &lt;el-select          v-model="queryParams.word"          filterable          placeholder="请输入"          clearable          :filter-method="filterMethod"          &gt;          &lt;el-option            v-for="word in words"            :key="word"            :label="word"            :value="word"          /&gt;        &lt;/el-select&gt;      &lt;/el-form-item&gt;    &lt;/el-form&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script setup name="word"&gt;import { pinyin } from 'pinyin-pro';const showSearch = ref(true);const words = ref(['你好', '世界', '中国', '中国最棒'])const wordsOld = ref(['你好', '世界', '中国', '中国最棒'])const data = reactive({  queryParams: {    word: null,  },});const { queryParams } = toRefs(data);// 多选框选中数据function filterMethod(val){  if(val){    const queryLower = val.toLowerCase();    words.value = []    // 筛选拼音首字母包含输入内容的选项    wordsOld.value.forEach(word =&gt; {      const firstLetterPinyin = convertToPinyin(word, 'first').replace(/\s+/g, '');      if(firstLetterPinyin.includes(queryLower)){        words.value.push(word)      }    })    // 筛选选包含输入内容的选项    wordsOld.value.forEach(word =&gt; {      if(word.includes(val)){        words.value.push(word)      }    })  } else {    words.value = wordsOld.value  }}function convertToPinyin(word, type) {  return pinyin(word, { pattern: type });}&lt;/script&gt;</code></pre><p>代码解析：</p><ol><li><p><strong>模板部分</strong>：</p><ul><li>使用 <code>el-select</code> 组件并启用 <code>filterable</code> 属性。</li><li>通过 <code>v-for</code> 渲染选项列表。</li></ul></li><li><p><strong>脚本部分</strong>：</p><ul><li>导入 <code>ref</code>, <code>reactive</code>, <code>toRefs</code> 从 Vue 中管理状态。</li><li>导入 <code>pinyin-pro</code> 库进行拼音转换。</li><li>定义原始选项列表 <code>wordsOld</code> 和当前选项列表 <code>words</code>。</li><li>定义一个响应式对象 <code>data</code> 存储查询参数。</li><li>实现 <code>filterMethod</code> 函数，根据输入值筛选匹配的选项，包括拼音首字母和汉字匹配。</li></ul></li></ol><p>以上代码实现了一个带拼音首字母匹配功能的下拉选择器，能有效地根据用户输入进行筛选。</p>]]>
                    </description>
                    <pubDate>Fri, 05 Jul 2024 15:00:07 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Java实现RS485串口通信]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/java-shi-xian-rs485-chuan-kou-tong-xin</link>
                    <description>
                            <![CDATA[<p>近期，我接到了一个任务，将报警器接入到Java项目中，而接入的方式就是通过RS485接入，本人之前可以说是对此毫无所知。不过要感谢现在的互联网，通过网络我查到了我想要知道的一切，这里记录下本次学习的情况，供大家参考</p><h1 id="一-RS485简单介绍">一、RS485简单介绍</h1><p>RS485是一种常用的串行通信标准，广泛应用于工业自动化和嵌入式系统。它采用差分信号传输，具有抗干扰能力强、传输距离远等优点。以下是关于RS485串口的一些关键点：</p><h2 id="1-硬件连接">1、硬件连接</h2><ul><li>RS485使用差分信号传输，通常需要使用收发器（如MAX485芯片）将串口的TTL信号转换为RS485信号</li><li>可以使用USB转RS485转换器实现与计算机的连接</li></ul><h2 id="2-通信方式">2、通信方式</h2><ul><li>RS485支持半双工通信，即发送和接收不能同时进行，通常需要软件控制来实现发送和接收的切换</li><li>通过两个数据线进行通信，数据线为A和B，A为正，B为负</li></ul><h2 id="3-数据发送和接收">3、数据发送和接收</h2><ul><li>在数据发送时，控制器的TX信号经过收发器转换成差分信号传输到总线上</li><li>接收时，差分信号通过收发器转换为TTL信号，再传输给控制器的RX端口</li><li>数据传输速率可以根据具体应用需求进行调整，常见的波特率有9600、19200等</li></ul><h1 id="二-电脑需要做的准备">二、电脑需要做的准备</h1><p>Windows系统还好，需要一个USB转RS485的转换器就可以了，基本不需要额外安装什么其他的。Linux系统可能就麻烦些，除了一个USB转RS485的转换器外，可能还需要下载相应的驱动（Linux这部分本人未实际操作，全凭网上的资料）。当然，如果你的电脑或者是设备本身就带RS485串口那就方便了，直接接上就好。</p><p>接线方面，A接T+，B接T-</p><h1 id="三-代码方面">三、代码方面</h1><p>本人使用的是Springboot项目，通过网上的查询，可以使用 <code>jSerialComm</code> 或 <code>RXTX</code> 库来实现串口通信。</p><h2 id="1-jSerialComm">1、jSerialComm</h2><p>本人觉得使用这个库相对简单些，直接在pom文件引入依赖就可以了，依赖代码如下：</p><pre><code class="language-xml">&lt;dependency&gt;    &lt;groupId&gt;com.fazecast&lt;/groupId&gt;    &lt;artifactId&gt;jSerialComm&lt;/artifactId&gt;    &lt;version&gt;2.9.2&lt;/version&gt;&lt;/dependency&gt;</code></pre><h2 id="2-RXTX">2、RXTX</h2><p>这个尼，个人觉得相对复杂些，首先，要先去下载RXTX的jar包(<a href="http://rxtx.qbang.org/pub/rxtx/rxtx-2.1-7-bins-r2.zip">rxtx-2.1-7-bins-r2</a>)，在使用时，除了在pom文件中引入压缩包内的<code>RXTXcomm.jar</code>包外，还需要在系统<code>%JAVA_HOME%/jre/bin</code>目录下放入对应的文件，比如说Windows的需要放入<code>rxtxParallel.dll</code>和<code>rxtxSerial.dll</code>两个文件</p><p>压缩包内容：</p><p><img src="https://img.huangge1199.cn/blog/javashi-xian-rs485chuan-kou-tong-xin/2024-06-24-11-58-06-image.png" alt=""></p><h2 id="3-代码例子">3、代码例子</h2><p>我这边用的<code>jSerialComm</code>的方式，引入jar包后，Java的测试代码如下：</p><pre><code class="language-java">import com.fazecast.jSerialComm.SerialPort;public class RS485Communication {    public static void main(String[] args) {        //SerialPort[] commPorts = SerialPort.getCommPorts();        //if (commPorts.length == 0) {        //    log.error("设备未插入！");        //}//SerialPort serialPort = null;        //if(SystemUtils.isLinux()){        //    for (SerialPort commPort : commPorts) {        //        log.info(JSON.toJSONString(commPort));        //        if(commPort.getSystemPortName().equals("ttyS2")){        //            serialPort = commPort;        //        }        //    }        //} else {        //    for (SerialPort commPort : commPorts) {        //        log.info(JSON.toJSONString(commPort));        //        if(commPort.getSystemPortName().contains("COM")){        //            serialPort = commPort;        //        }        //    }        //}    // 这里取的是第一个，如果你有多个，可以通过注释的代码来确定你使用的是哪一个        SerialPort serialPort = SerialPort.getCommPorts()[0];        serialPort.setBaudRate(9600);        serialPort.setNumDataBits(8);        serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT);        serialPort.setParity(SerialPort.NO_PARITY);        if (serialPort.openPort()) {            System.out.println("Port opened successfully.");        } else {            System.out.println("Failed to open port.");            return;        }        // 发送数据        byte[] dataToSend = {0x00, 0x10}; // 示例16位数据        serialPort.writeBytes(dataToSend, dataToSend.length);        // 接收数据        byte[] readBuffer = new byte[2];        serialPort.readBytes(readBuffer, readBuffer.length);        System.out.println("Received: " + bytesToHex(readBuffer));        serialPort.closePort();    }    private static String bytesToHex(byte[] bytes) {        StringBuilder sb = new StringBuilder();        for (byte b : bytes) {            sb.append(String.format("%02X ", b));        }        return sb.toString().trim();    }}</code></pre><pre><code></code></pre>]]>
                    </description>
                    <pubDate>Mon, 24 Jun 2024 18:10:11 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[TS（TypeScript）看这篇就够了]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/tstypescript-kan-zhe-pian-jiu-gou-le</link>
                    <description>
                            <![CDATA[<h1 id="Typescript-简介">Typescript 简介</h1><p>TypeScript是用于应用程序规模开发的JavaScript。</p><p>TypeScript是强类型，面向对象的编译语言。它是由微软的Anders Hejlsberg（C＃的设计者）设计的。</p><p>TypeScript既是一种语言又是一组工具。TypeScript是JavaScript的一个超集。换句话说，TypeScript是JavaScript加上一些额外的功能。</p><p>TypeScript 扩展了 JavaScript 的语法，所以任何现有的 JavaScript 程序可以不加改变的在 TypeScript 下工作。TypeScript 是为大型应用之开发而设计，而编译时它产生 JavaScript 以确保兼容性。</p><p>TypeScript 可以编译出纯净、 简洁的 JavaScript 代码，并且可以运行在任何浏览器上、Node.js 环境中和任何支持 ECMAScript 3（或更高版本）的 JavaScript 引擎中。</p><h1 id="TypeScript-的优势">TypeScript 的优势</h1><p>TypeScript相对于纯粹的JavaScript具有许多优势，特别是在开发大型应用程序时。以下是一些TypeScript的优势：</p><h2 id="静态类型系统">静态类型系统</h2><p>TypeScript引入了静态类型系统，允许开发者在声明变量、函数参数、返回值等时指定类型。这种静态类型检查可以帮助捕获常见的编程错误，例如类型不匹配、未定义的属性或方法等，提供更好的代码质量和可靠性。</p><h2 id="更好的代码智能感知">更好的代码智能感知</h2><p>因为TypeScript了解代码中的类型信息，因此编辑器可以提供更准确和强大的代码智能感知和自动补全功能。这可以显著提高开发效率，并减少常见的编码错误。</p><h2 id="更易于重构和维护">更易于重构和维护</h2><p>静态类型和面向对象特性使得代码更模块化、更结构化，从而更易于重构和维护。IDE可以更好地支持重构操作，并能够更好地理解代码的结构和依赖关系。</p><h2 id="更丰富的面向对象特性">更丰富的面向对象特性</h2><p>TypeScript支持类、接口、继承、多态等面向对象编程的特性，使得代码组织更清晰、更易于理解。这对于构建大型应用程序非常有用。</p><h2 id="更好的工具支持-">更好的工具支持：</h2><p>TypeScript配合现代的集成开发环境（如VS Code、WebStorm等），可以提供强大的代码导航、重构、调试和代码分析工具。此外，TypeScript还能够与许多流行的前端框架（如Angular、React等）良好集成。</p><h2 id="增强的语言功能-">增强的语言功能：</h2><p>TypeScript不仅仅是JavaScript的超集，它还引入了一些新的语言功能，如箭头函数、可选参数、默认参数、模板字符串等，使得代码更简洁和易读。</p><h2 id="更好的生态系统-">更好的生态系统：</h2><p>TypeScript拥有庞大的社区支持，许多常用的JavaScript库和框架都提供了类型定义文件，可以轻松地与TypeScript集成。这使得使用第三方库时具有更好的类型安全性和开发体验。</p><h1 id="基础类型">基础类型</h1><p>TypeScript支持与JavaScript几乎相同的数据类型 数字，字符串，结构体，布尔值等，此外还提供了实用的枚举类型方便我们使用。</p><h2 id="布尔值">布尔值</h2><p>最基本的数据类型就是简单的 true/false 值，在 JavaScript 和 TypeScript 里叫做 boolean（其它语言中也一样）。 我们来定义一个布尔类型的变量:</p><pre><code class="language-ts">let isDone: boolean = false;</code></pre><p>在TypeScript中, 在参数名称后面使用冒号:来指定参数的类型</p><pre><code class="language-ts">let 变量名: 数据类型</code></pre><h2 id="数字">数字</h2><p>和 JavaScript 一样，TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量，TypeScript 还支持 ECMAScript 2015 中引入的二进制和八进制字面量。</p><pre><code class="language-ts">let decLiteral: number = 6;let hexLiteral: number = 0xf00d;let binaryLiteral: number = 0b1010;let octalLiteral: number = 0o744;</code></pre><h2 id="字符串">字符串</h2><h3 id="字符串新特性">字符串新特性</h3><p>JavaScript 程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样，我们使用 string 表示文本数据类型。 和 JavaScript 一样，可以使用双引号 "或单引号'表示字符串。</p><pre><code class="language-ts">let name: string = "bob";name = "loen";</code></pre><p>以上字符串不支持换行.</p><h3 id="多行字符串">多行字符串</h3><p>在Typescript中你可以使用反引号 ` 表示多行字符串.</p><pre><code class="language-ts">let hello: string = `Welcome to W3cschool`;</code></pre><h3 id="内嵌表达式">内嵌表达式</h3><p>你还可以使用模版字符串，也就是在反引号中使用 ${ expr }这种形式嵌入表达式</p><pre><code class="language-ts">let name: string = `Loen`;let age: number = 37;let sentence: string = `Hello, my name is ${ name }.I'll be ${ age + 1 } years old next month.`;</code></pre><p>这与下面定义sentence的方式效果相同：</p><pre><code class="language-ts">let sentence: string = "Hello, my name is " + name + ".\n\n" +    "I'll be " + (age + 1) + " years old next month.";</code></pre><blockquote><p>我们可以看到Typescript定义的字符串更加清晰简单.</p></blockquote><h3 id="自动拆分字符串">自动拆分字符串</h3><p>我们可以用字符串模板去调用一个方法</p><pre><code class="language-ts">function userinfo(params,name,age){    console.log(params);    console.log(name);    console.log(age);}let myname = "Loen Wang";let getAge = function(){    return 18;}// 调用userinfo`hello my name is ${myname}, i'm ${getAge()}`</code></pre><p>结果：<br><img src="https://img.huangge1199.cn/blog/ts%EF%BC%88typescript%EF%BC%89-kan-zhe-pian-jiu-gou-le/2024-04-23-16-18-22-image.png" alt=""></p><h2 id="数组">数组</h2><p>TypeScript 有两种方式可以定义数组。</p><p>第一种, 是在元素类型后面接上&nbsp;<code>[]</code>，表示由此类型元素组成的一个数组：</p><pre><code class="language-ts">let list: number[] = [1, 2, 3];</code></pre><p>第二种方式是使用数组泛型，Array&lt;元素类型&gt;：</p><pre><code class="language-ts">let list: Array&lt;number&gt; = [1, 2, 3];</code></pre><h2 id="元组-Tuple">元组 Tuple</h2><p>元组类型允许表示一个已知元素数量和类型的数组，各元素的类型不必相同。 比如，你可以定义一对值分别为&nbsp;<code>string</code>&nbsp;和&nbsp;<code>number</code>&nbsp;类型的元组。</p><pre><code class="language-ts">// 声明一个元组类型let x: [string, number];// 初始化元组x = ['hello', 10]; x = [10, 'hello']; // 这里会报错,类型错误</code></pre><h2 id="枚举">枚举</h2><p><code>enum</code>&nbsp;类型是对 JavaScript 标准数据类型的一个补充。 像 C# 等其它语言一样，使用枚举类型可以为一组数值赋予友好的名字。</p><pre><code class="language-ts">enum Color {Red, Green, Blue}let c: Color = Color.Green;</code></pre><p>默认情况下，<strong>从0开始为元素编号</strong>。 你也可以手动的指定成员的数值。 例如，我们将上面的例子改成从 1开始编号：</p><pre><code class="language-ts">enum Color {Red = 1, Green, Blue}let c: Color = Color.Green;</code></pre><p>或者，全部都采用手动赋值：</p><pre><code class="language-ts">enum Color {Red = 1, Green = 2, Blue = 4}let c: Color = Color.Green;</code></pre><p>枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如，我们知道数值为2，但是不确定它映射到Color里的哪个名字，我们可以查找相应的名字：</p><pre><code class="language-ts">enum Color {Red = 1, Green, Blue}let colorName: string = Color[2];alert(colorName);  // 显示'Green'因为上面代码里它的值是2</code></pre><h2 id="Any">Any</h2><p>如果不希望类型检查器对值进行检查,直接通过编译阶段的检查。 那么我们可以使用&nbsp;<code>any</code>类型来标记这些变量：</p><pre><code class="language-ts">let notSure: any = 4;notSure = "这是一个字符串";notSure = false; // 现在我们又可以将其改成布尔类型</code></pre><p>在对现有代码进行改写的时候，<code>any</code>类型是十分有用的，它允许你在编译时可选择地包含或移除类型检查。 你可能认为 Object有相似的作用，就像它在其它语言中那样。 但是 Object类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法，即便它真的有这些方法：</p><pre><code class="language-ts">let notSure: any = 4;notSure.ifItExists();// 存在这个方法notSure.toFixed(); // 存在这个方法let prettySure: Object = 4;prettySure.toFixed(); // 错误：对象类型上不存在 toFixed 属性</code></pre><p>当你只知道一部分数据的类型时，<code>any</code>类型也是有用的。 比如，你有一个数组，它包含了不同的类型的数据：</p><pre><code class="language-ts">let list: any[] = [1, true, "free"];list[1] = 100;</code></pre><h2 id="Void">Void</h2><p>某种程度上来说，<code>void</code>类型像是与<code>any</code>类型相反，它表示没有任何类型。 当一个函数没有返回值时，你通常会见到其返回值类型是&nbsp;<code>void</code>：</p><pre><code class="language-ts">function warnUser(): void {    alert("This is my warning message");}</code></pre><p>声明一个<code>void</code>类型的变量没有什么大用，因为你只能为它赋予<code>undefined</code>和<code>null</code>：</p><pre><code class="language-ts">let unusable: void = undefined;</code></pre><h2 id="Null-和-Undefined">Null 和 Undefined</h2><p>TypeScript 里，<code>undefined</code>&nbsp;和&nbsp;<code>null</code>&nbsp;两者各自有自己的类型分别叫做&nbsp;<code>undefined</code>&nbsp;和&nbsp;<code>null</code>。 和&nbsp;<code>void</code>&nbsp;相似，它们的本身的类型用处不是很大：</p><pre><code class="language-ts">// 我们无法给这些变量赋值let u: undefined = undefined;let n: null = null;</code></pre><p>默认情况下&nbsp;<strong>null 和 undefined 是所有类型的子类型</strong>。</p><p>就是说你可以把 null 和 undefined 赋值给 number 类型的变量。</p><p>然而，当你编译时指定了 --strictNullChecks 标记， null 和 undefined 只能赋值给 void 和它们自己。</p><blockquote><p>注意：我们鼓励尽可能地使用<code>--strictNullChecks</code>，但在本教程里我们假设这个标记是关闭的。</p></blockquote><h2 id="Never">Never</h2><p><code>never</code>&nbsp;类型表示的是那些永不存在的值的类型。</p><p>例如， never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型；</p><p><code>never</code>&nbsp;类型是任何类型的子类型，也可以赋值给任何类型； 然而，没有类型是 never 的子类型或可以赋值给 never 类型（除了 never 本身之外）。 即使 any 也不可以赋值给 never 。</p><p>下面是一些返回 never 类型的函数：</p><pre><code class="language-ts">// 返回never的函数必须存在无法达到的终点function error(message: string): never {    throw new Error(message);}// 推断的返回值类型为neverfunction fail() {    return error("Something failed");}// 返回never的函数必须存在无法达到的终点function infiniteLoop(): never {    while (true) {    }}</code></pre><blockquote><p>箭头表达式将再后面的课程中学习到。</p></blockquote><h2 id="类型断言">类型断言</h2><p>通过类型断言这种方式可以告诉编译器，“相信我，我知道自己在干什么”。 类型断言好比其它语言里的类型转换，但是不进行特殊的数据检查和解构。 它没有运行时的影响，只是在编译阶段起作用。</p><p>TypeScript 会假设你，程序员，已经进行了必须的检查。</p><p>类型断言有两种形式。 其一是<code>尖括号</code>语法：</p><pre><code class="language-ts">let someValue: any = "this is a string";let strLength: number = (&lt;string&gt;someValue).length;</code></pre><p>另一个为<code>as</code>语法：</p><pre><code class="language-ts">let someValue: any = "this is a string";let strLength: number = (someValue as string).length;</code></pre><p>两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好；</p><p>然而，当你在 TypeScript 里使用&nbsp;<code>JSX</code>&nbsp;时，只有&nbsp;<code>as</code>语法断言是被允许的。</p><h1 id="符号介绍">符号介绍</h1><p>自ECMAScript 2015起，symbol成为了一种新的原生类型，就像number和string一样。</p><p>symbol类型的值是通过Symbol构造函数创建的。</p><pre><code class="language-ts">let sym1 = Symbol();let sym2 = Symbol("key"); // 可选的字符串key</code></pre><p>Symbols是不可改变且唯一的。</p><pre><code class="language-ts">let sym2 = Symbol("key");let sym3 = Symbol("key");sym2 === sym3; // false</code></pre><p>symbols是唯一的像字符串一样，symbols也可以被用做对象属性的键。</p><pre><code class="language-ts">let sym = Symbol();let obj = {    [sym]: "value"};console.log(obj[sym]); // "value"</code></pre><p>Symbols也可以与计算出的属性名声明相结合来声明对象的属性和类成员。</p><pre><code class="language-ts">const getClassNameSymbol = Symbol();class C {    [getClassNameSymbol](){       return "C";    }}let c = new C();let className = c[getClassNameSymbol](); // "C"</code></pre><h1 id="变量声明">变量声明</h1><h2 id="let和const">let和const</h2><p><code>let</code>和<code>const</code>是JavaScript里相对较新的变量声明方式。 像我们之前提到过的， let在很多方面与<code>var</code>是相似的，但是可以帮助大家避免在JavaScript里常见一些问题。&nbsp;<code>const</code>只能一次赋值, 再次赋值会报错。</p><ul><li>let可以多次写入</li><li>const只允许一次写入</li></ul><p>因为 TypeScript 是 JavaScript 的超集，所以它本身就支持let和const。 下面我们会详细说明这些新的声明方式以及为什么推荐使用它们来代替&nbsp;<code>var</code>。</p><h2 id="var-声明">var 声明</h2><p>一直以来我们都是通过<code>var</code>关键字定义 JavaScript 变量。</p><pre><code class="language-ts">var a = 10;</code></pre><p>大家都能理解，这里定义了一个名为a值为10的变量。</p><p>我们也可以在函数内部定义变量：</p><pre><code class="language-ts">function f() {    var message = "Hello, world!";    return message;}</code></pre><p>并且我们也可以在其它函数内部访问相同的变量。</p><pre><code class="language-ts">function f() {    var a = 10;    return function g() {        var b = a + 1;        return b;    }}var g = f();g(); // returns 11;</code></pre><p>上面的例子里，g 可以获取到 f 函数里定义的 a 变量。 每当 g 被调用时，它都可以访问到 f 里的 a 变量。 即使当 g 在 f 已经执行完后才被调用，它仍然可以访问及修改 a 。</p><pre><code class="language-ts">function f() {    var a = 1;    a = 2;    var b = g();    a = 3;    return b;    function g() {        return a;    }}f(); // returns 2</code></pre><h3 id="作用域规则">作用域规则</h3><p>对于熟悉其它语言的人来说，<code>var</code>声明有些奇怪的作用域规则。 看下面的例子：</p><pre><code class="language-ts">function f(shouldInitialize: boolean) {    if (shouldInitialize) {        var x = 10;    }    return x;}f(true);  // returns '10'f(false); // returns 'undefined'</code></pre><p>变量 x 是定义在&nbsp;<em>if 语句里面</em>&nbsp;，但是我们却可以在语句的外面访问它。 这是因为&nbsp;<code>var</code>声明可以在包含它的函数，模块，命名空间或全局作用域内部任何位置被访问，包含它的代码块对此没有什么影响。</p><p>这些作用域规则可能会引发一些错误。 其中之一就是，多次声明同一个变量并不会报错：</p><pre><code class="language-ts">function sumMatrix(matrix: number[][]) {    var sum = 0;    for (var i = 0; i &lt; matrix.length; i++) {        var currentRow = matrix[i];        for (var i = 0; i &lt; currentRow.length; i++) {            sum += currentRow[i];        }    }    return sum;}</code></pre><p>这里很容易看出一些问题，里层的 for 循环会覆盖变量 i，因为所有 i 都引用相同的函数作用域内的变量。 这很容易引发无穷的麻烦。</p><h2 id="let-声明">let 声明</h2><p>现在你已经知道了<code>var</code>存在一些问题，这恰好说明了为什么用<code>let</code>语句来声明变量。</p><pre><code class="language-ts">let hello = "Hello!";</code></pre><h3 id="块作用域">块作用域</h3><p>当用&nbsp;<code>let</code>&nbsp;声明一个变量，它使用的是词法作用域或块作用域。 不同于使用&nbsp;<code>var</code>&nbsp;声明的变量那样可以在包含它们的函数外访问，块作用域变量在包含它们的块或&nbsp;<code>for</code>&nbsp;循环之外是不能访问的。</p><pre><code class="language-ts">function f(input: boolean) {    let a = 100;    if (input) {        // Still okay to reference 'a'        let b = a + 1;        return b;    }    // Error: 'b' doesn't exist here    return b;}</code></pre><p>这里我们定义了2个变量 a 和 b 。 a 的作用域是 f 函数体内，而 b 的作用域是 if 语句块里。</p><p>在<code>catch</code>语句里声明的变量也具有同样的作用域规则。</p><pre><code class="language-ts">try {    throw "oh no!";}catch (e) {    console.log("Oh well.");}// Error: 'e' doesn't exist hereconsole.log(e);</code></pre><p>拥有块级作用域的变量的另一个特点是，它们不能在被声明之前读或写。</p><p>虽然这些变量始终<code>“存在”</code>于它们的作用域里，但在直到声明它的代码之前的区域都属于 暂时性死区。 它只是用来说明我们不能在&nbsp;<code>let</code>语句之前访问它们，幸运的是 TypeScript 可以告诉我们这些信息。</p><pre><code class="language-ts">a++; // illegal to use 'a' before it's declared;let a;</code></pre><p><strong>注意</strong>: 我们仍然可以在一个拥有块作用域变量被声明前获取它。 只是我们不能在变量声明前去调用那个函数。 如果生成代码目标为ES2015，现代的运行时会抛出一个错误；然而，现今 TypeScript 是不会报错的。</p><pre><code class="language-ts">function foo() {    // okay to capture 'a'    return a;}// 不能在'a'被声明前调用'foo'// 运行时应该抛出错误foo();let a;</code></pre><h2 id="重定义及屏蔽">重定义及屏蔽</h2><p>我们提过使用&nbsp;<code>var</code>&nbsp;声明时，它不在乎你声明多少次；你只会得到1个。</p><pre><code class="language-ts">function f(x) {    var x;    var x;    if (true) {        var x;    }}</code></pre><p>在上面的例子里，所有<code>x</code>的声明实际上都引用一个相同的<code>x</code>，并且这是完全有效的代码。 这经常会成为<code>bug</code>的来源。 好的是，&nbsp;<code>let</code>声明就不会这么宽松了。</p><pre><code class="language-ts">let x = 10;let x = 20; // 错误，不能在1个作用域里多次声明`x`</code></pre><p>并不是要求两个均是块级作用域的声明 TypeScript 才会给出一个错误的警告。</p><pre><code class="language-ts">function f(x) {    let x = 100; // error: interferes with parameter declaration}function g() {    let x = 100;    var x = 100; // 错误：不能同时声明'x'}</code></pre><p>并不是说块级作用域变量不能用函数作用域变量来声明。 而是块级作用域变量需要在明显不同的块里声明。</p><pre><code class="language-ts">function f(condition, x) {    if (condition) {        let x = 100;        return x;    }    return x;}f(false, 0); // returns 0f(true, 0);  // returns 100</code></pre><p>在一个嵌套作用域里引入一个新名字的行为称做屏蔽。 它是一把双刃剑，它可能会不小心地引入新问题，同时也可能会解决一些错误。 例如，假设我们现在用&nbsp;<code>let</code>重写之前的<code>sumMatrix</code>函数。</p><pre><code class="language-ts">function sumMatrix(matrix: number[][]) {    let sum = 0;    for (let i = 0; i &lt; matrix.length; i++) {        var currentRow = matrix[i];        for (let i = 0; i &lt; currentRow.length; i++)      {            sum += currentRow[i];        }    }    return sum;}</code></pre><p>这个版本的循环能得到正确的结果，因为内层循环的<code>i</code>可以屏蔽掉外层循环的<code>i</code>。</p><p>通常来讲应该避免使用这种屏蔽，因为我们需要写出清晰的代码。</p><h2 id="块级作用域变量的获取">块级作用域变量的获取</h2><p><code>let</code>声明每次迭代都会创建一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事，所以在&nbsp;<code>setTimeout</code>&nbsp;例子里我们仅使用&nbsp;<code>let</code>&nbsp;声明就可以了。</p><pre><code class="language-ts">for (let i = 0; i &lt; 10 ; i++) {    setTimeout(function() {        console.log(i);     }, 100 * i);}</code></pre><p>会输出与预料一致的结果：</p><pre><code>0123456789</code></pre><h2 id="const-声明">const 声明</h2><p><code>const</code>&nbsp;声明是声明变量的另一种方式。</p><pre><code class="language-ts">const numLivesForCat = 9;</code></pre><p>const声明的变量只允许一次赋值, 引用的值是不可变的。</p><pre><code class="language-ts">const numLivesForCat = 9;const kitty = {    name: "Aurora",    numLives: numLivesForCat,}// 重新赋值一个类会报错kitty = {    name: "Loen",    numLives: numLivesForCat};// 属性修改是允许的kitty.name = "Rory";kitty.name = "Kitty";kitty.name = "Cat";kitty.numLives--;</code></pre><p>除非你使用特殊的方法去避免，实际上<code>const</code>变量的内部状态是可修改的。 幸运的是，TypeScript允许你将对象的成员设置成只读的。</p><h1 id="解构">解构</h1><h2 id="解构数组">解构数组</h2><p>最简单的解构莫过于数组的解构赋值了：</p><pre><code class="language-ts">let input = [1, 2];let [first, second] = input;console.log(first); // outputs 1console.log(second); // outputs 2</code></pre><p>这创建了2个命名变量&nbsp;<code>first</code>&nbsp;和&nbsp;<code>second</code>。 相当于使用了索引，但更为方便：</p><pre><code class="language-ts">first = input[0];second = input[1];</code></pre><p>解构作用于已声明的变量会更好：</p><pre><code class="language-ts">// 对换变量的值[first, second] = [second, first];</code></pre><p>作用于函数参数：</p><pre><code class="language-ts">function f([first, second]: [number, number]) {    console.log(first);    console.log(second);}f(input);</code></pre><p>你可以在数组里使用<code>...</code>语法创建剩余变量：</p><pre><code class="language-ts">let [first, ...rest] = [1, 2, 3, 4];console.log(first); // outputs 1console.log(rest); // outputs [ 2, 3, 4 ]</code></pre><p>当然，由于是 JavaScript, 你可以忽略你不关心的尾随元素：</p><pre><code class="language-ts">let [first] = [1, 2, 3, 4];console.log(first); // outputs 1</code></pre><p>或其它元素：</p><pre><code class="language-ts">let [, second, , fourth] = [1, 2, 3, 4];</code></pre><h2 id="对象解构">对象解构</h2><p>你也可以解构对象：</p><pre><code class="language-ts">let o = {    a: "foo",    b: 12,    c: "bar"};let { a, b } = o;</code></pre><p>这通过&nbsp;<code>o.a and o.b</code>&nbsp;创建了&nbsp;<code>a</code>&nbsp;和&nbsp;<code>b</code>&nbsp;。 注意，如果你不需要&nbsp;<code>c</code>&nbsp;你可以忽略它。</p><p>就像数组解构，你可以用没有声明的赋值：</p><pre><code class="language-ts">({ a, b } = { a: "baz", b: 101 });</code></pre><p><strong>注意</strong>:我们需要用括号将它括起来，因为Javascript通常会将以 { 起始的语句解析为一个块。</p><p>你可以在对象里使用...语法创建剩余变量：</p><pre><code class="language-ts">let { a, ...passthrough } = o;let total = passthrough.b + passthrough.c.length;</code></pre><h3 id="属性重命名">属性重命名</h3><p>你也可以给属性以不同的名字：</p><pre><code class="language-ts">let { a: newName1, b: newName2 } = o;</code></pre><p>这里的语法开始变得混乱。 你可以将&nbsp;<code>a: newName1</code>&nbsp;读做&nbsp;<code>a 作为 newName1</code>。 方向是从左到右，好像你写成了以下样子：</p><pre><code class="language-ts">let newName1 = o.a;let newName2 = o.b;</code></pre><p>令人困惑的是，这里的冒号不是指示类型的。 如果你想指定它的类型， 仍然需要在其后写上完整的模式。</p><pre><code class="language-ts">let {a, b}: {a: string, b: number} = o;</code></pre><h3 id="默认值">默认值</h3><p>默认值可以让你在属性为&nbsp;<code>undefined</code>&nbsp;时使用缺省值：</p><pre><code class="language-ts">function keepWholeObject(wholeObject: { a: string, b?: number }) {    let { a, b = 1001 } = wholeObject;}</code></pre><p>现在，即使&nbsp;<code>b</code>&nbsp;为 undefined ， keepWholeObject 函数的变量 wholeObject 的属性 a 和 b 都会有值。</p><h2 id="函数声明">函数声明</h2><p>解构也能用于函数声明。 看以下简单的情况：</p><pre><code class="language-ts">type C = { a: string, b?: number }function f({ a, b }: C): void {    // ...}</code></pre><p>通常情况下更多的是指定默认值，解构默认值有些棘手。 首先，你需要在默认值之前设置其格式。</p><pre><code class="language-ts">function f({ a, b } = { a: "", b: 0 }): void {    // ...}f(); // 默认 { a: "", b: 0 }</code></pre><p>你需要知道在解构属性上给予一个默认或可选的属性用来替换主初始化列表。 要知道 C 的定义有一个 b 可选属性：</p><pre><code class="language-ts">function f({ a, b = 0 } = { a: "" }): void {    // ...}f({ a: "yes" }); // 默认 b = 0f(); // 默认 {a: ""},  b = 0f({}); // 错误, 如果您提供参数，则需要'a'</code></pre><p>从前面的例子可以看出, 要小心使用解构。就算是最简单的解构表达式也是难以理解的。 尤其当存在深层嵌套解构的时候，就算这时没有堆叠在一起的重命名，默认值和类型注解，也是令人难以理解的。</p><blockquote><p>解构表达式要尽量保持小而简单。</p></blockquote><h1 id="展开">展开</h1><p>展开操作符正与解构相反。 它允许你将一个数组展开为另一个数组，或将一个对象展开为另一个对象。 例如：</p><pre><code class="language-ts">let first = [1, 2];let second = [3, 4];let bothPlus = [0, ...first, ...second, 5];</code></pre><p>这会令<code>bothPlus</code>的值为 [0, 1, 2, 3, 4, 5] 。 展开操作创建了 first 和 second 的一份浅拷贝。 它们不会被展开操作所改变。</p><p>你还可以展开对象：</p><pre><code class="language-ts">let defaults = { food: "spicy", price: "$", ambiance: "noisy" };let search = { ...defaults, food: "rich" };</code></pre><p><code>search</code>的值为 { food: "rich", price: "$", ambiance: "noisy" } 。 对象的展开比数组的展开要复杂的多。 像数组展开一样，它是从左至右进行处理，但结果仍为对象。 这就意味着出现在展开对象后面的属性会覆盖前面的属性。 因此，如果我们修改上面的例子，在结尾处进行展开的话：</p><pre><code class="language-ts">let defaults = { food: "spicy", price: "$", ambiance: "noisy" };let search = { food: "rich", ...defaults };</code></pre><p>那么， defaults 里的 food 属性会重写 food: "rich" ，在这里这并不是我们想要的结果。</p><p>对象展开还有其它一些意想不到的限制。 首先，它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时，你会丢失其方法：</p><pre><code class="language-ts">class C {  p = 12;  m() {  }}let c = new C();let clone = { ...c };clone.p; // 没问题clone.m(); // 错误</code></pre><h1 id="函数">函数</h1><h2 id="函数介绍">函数介绍</h2><p>函数是JavaScript应用程序的基础。 它帮助你实现抽象层，模拟类，信息隐藏和模块。 在TypeScript里，虽然已经支持类，命名空间和模块，但函数仍然是主要的定义 行为的地方。 TypeScript为JavaScript函数添加了额外的功能，让我们可以更容易地使用。</p><h3 id="Typescript-函数">Typescript 函数</h3><p>和JavaScript一样，TypeScript函数可以创建有名字的函数和匿名函数。 你可以随意选择适合应用程序的方式，不论是定义一系列API函数还是只使用一次的函数。</p><p>通过下面的例子可以迅速回想起这两种JavaScript中的函数：</p><pre><code class="language-ts">// 命名函数function add(x, y) {    return x + y;}// 匿名函数let myAdd = function(x, y) { return x + y; };</code></pre><h2 id="函数类型">函数类型</h2><p><strong>为函数定义类型</strong>&nbsp;我们可以为函数本身添加返回值类型。</p><pre><code class="language-ts">函数():类型 {}</code></pre><p>我们给函数添加类型：</p><pre><code class="language-ts">function add(x: number, y: number): number {    return x + y;}let myAdd = function(x: number, y: number): number { return x + y; };</code></pre><p>TypeScript能够根据返回语句自动推断出返回值类型，因此我们通常省略它。</p><h2 id="函数参数">函数参数</h2><p>TypeScript里的每个函数参数都是必须的。 传递给一个函数的参数个数必须与函数期望的参数个数一致。</p><pre><code class="language-ts">function buildName(firstName: string, lastName: string) {    return firstName + " " + lastName;}// error, too few parameterslet result1 = buildName("Bob");// error, too many parameterslet result2 = buildName("Bob", "Adams", "Sr.");// 这种方式是正确的let result3 = buildName("Bob", "Adams");</code></pre><h3 id="可选参数">可选参数</h3><p>在TypeScript里我们可以在参数名旁使用 ? 实现可选参数的功能。 比如，我们想让last name是可选的：</p><pre><code class="language-ts">function buildName(firstName: string, lastName?: string) {    if (lastName)        return firstName + " " + lastName;    else        return firstName;}// 现在这样也可以let result1 = buildName("Bob"); // error, too many parameterslet result2 = buildName("Bob", "Adams", "Sr.");// 这种方式是正确的let result3 = buildName("Bob", "Adams");</code></pre><p>注意: 可选参数必须跟在必须参数后面。 如果上例我们想让first name是可选的，那么就必须调整它们的位置，把first name放在后面。</p><h3 id="默认参数">默认参数</h3><p>在TypeScript里，我们也可以为参数提供一个默认值。让我们修改上例，把last name的默认值设置为"Smith"。</p><pre><code class="language-ts">function buildName(firstName: string, lastName = "Smith") {    return firstName + " " + lastName;}// 这样是可以工作的 返回 "Bob Smith"let result1 = buildName("Bob");// 这样也可以工作返回 "Bob Smith"let result2 = buildName("Bob", undefined);// error, too many parameterslet result3 = buildName("Bob", "Adams", "Sr.");// 这是正确的返回 "Bob Adams"let result4 = buildName("Bob", "Adams");</code></pre><h3 id="剩余参数">剩余参数</h3><p>当你想同时操作多个参数，而你并不知道会有多少参数传递进来。 在JavaScript里，你可以使用 arguments来访问所有传入的参数。 而在TypeScript里，你可以使用&nbsp;<strong>...变量名</strong>&nbsp;把所有参数收集到一个变量里：</p><pre><code class="language-ts">function buildName(firstName: string, ...restOfName: string[]) {  return firstName + " " + restOfName.join(" ");}let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");</code></pre><p>剩余参数会被当做个数不限的可选参数。 可以一个都没有，同样也可以有任意个。</p><p>这个省略号也会在带有剩余参数的函数类型定义上使用到：</p><pre><code class="language-ts">function buildName(firstName: string, ...restOfName: string[]) {  return firstName + " " + restOfName.join(" ");}</code></pre><h2 id="箭头函数">箭头函数</h2><h3 id="表现形式">表现形式</h3><p>基本语法 ES6 允许使用“箭头”（=&gt;）定义函数 箭头函数相当于匿名函数，并且简化了函数定义&nbsp;<strong>表现形式一：</strong>&nbsp;包含一个表达式，连{ ... }和return都省略掉了</p><pre><code class="language-ts"> x =&gt; x * x//等同于function (x) {  return x*x;};</code></pre><p><strong>表示形式二：</strong>&nbsp;包含多条语句，这时候就不能省略{ ... }和return</p><pre><code class="language-ts">x =&gt; {if (x &gt; 0) {    return x * x;}else {    return - x * x;}}</code></pre><h3 id="this">this</h3><p>箭头函数的引入有两个方面的作用：</p><ul><li>一是更简短的函数书写</li><li>二是对this的词法解析。</li></ul><p>普通函数: this指向调用它的那个对象 箭头函数:不绑定this，会捕获其所在的上下文的this值，作为自己的this值,任何方法都改变不了其指向，如: call(),bind(),apply()</p><pre><code class="language-ts">var obj = {a: 10,b: () =&gt; {console.log('b this.a:',this.a); // undefinedconsole.log('b this:',this); // Window }, c: function() {console.log('c this.a:',this.a); // 10console.log('c this:',this); // {a: 10, b: ƒ, c: ƒ}} }obj.b(); obj.c();</code></pre><p>执行结果:&nbsp;<img src="https://www.w3cschool.cn/attachments/image/20190620/1561021902153358.png" alt=""></p><h2 id="函数重载">函数重载</h2><p>所谓函数重载就是同一个函数，根据传递的参数不同，会有不同的表现形式。</p><p>JavaScript本身是没有重载这个概念，不过可以模拟实现。 JavaScript 代码实例如下：</p><pre><code class="language-js">function func(){   if(arguments.length==0){     alert("欢迎来到w3cschool");    }   else if(arguments.length==1){     alert(arguments[0])   } } func(); func(2);</code></pre><p>上面代码利用arguments对象来判断传递参数的数量，然后执行不同的代码。</p><h3 id="TypeScript-函数重载">TypeScript 函数重载</h3><p>TypeScript提供了重载功能，TypeScript的函数重载只有一个函数体，也就是说无论声明多少个同名且不同签名的函数，它们共享一个函数体，在调用时会根据传递实参类型的不同，利用流程控制语句控制代码的执行。</p><p>TypeScript代码实例如下:</p><pre><code class="language-ts">function func(x:string):string;function func(x:number):number;function func(x:any):any{  if(typeof x=="string"){    return "欢迎来到w3cschool"  }else if(typeof x=="number"){    return 5  }}</code></pre><p>function func(x:any):any不是函数重载列表一部分，所以上述代码只定义两个重载。</p><p>重载函数的共用函数体部分如下：</p><pre><code class="language-ts">function func(x:any):any{  if(typeof x=="string"){    return "欢迎来到w3cschool"  }else if(typeof x=="number"){    return 5  }}</code></pre><p>重载函数编译后的JavaScript代码:</p><pre><code class="language-js">function func(x) {  if (typeof x == "string") {    return "欢迎来到w3cschool";  }  else if (typeof x == "number") {    return 5;  }}</code></pre><p>由于JavaScript本身不支持重载，所以TypeScript重载实质上为了方便调用者如何调用函数。</p><h1 id="接口">接口</h1><h2 id="接口介绍">接口介绍</h2><p>在TypeScript里，接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。</p><h3 id="接口初探">接口初探</h3><p>下面通过一个简单示例来观察接口是如何工作的：</p><pre><code class="language-ts">function printLabel(labelledObj: { label: string }) {  console.log(labelledObj.label);}let myObj = { size: 10, label: "Size 10 Object" };printLabel(myObj);</code></pre><p>类型检查器会查看 printLabel 的调用。 printLabel 有一个参数，并要求这个对象参数有一个名为 label 类型为 string 的属性。</p><p>需要注意的是，我们传入的对象参数实际上会包含很多属性，但是编译器只会检查那些必需的属性是否存在，并且其类型是否匹配。</p><p>下面我们重写上面的例子，这次使用接口来描述：必须包含一个 label 属性且类型为 string ：</p><pre><code>interface LabelledValue {  label: string;}function printLabel(labelledObj: LabelledValue) {  console.log(labelledObj.label);}let myObj = {size: 10, label: "Size 10 Object"};printLabel(myObj);</code></pre><p><code>LabelledValue</code>接口就好比一个名字，用来描述上面例子里的要求。 它代表了有一个 label 属性且类型为 string 的对象。</p><p>只要传入的对象满足上面提到的必要条件，那么它就是被允许的。</p><p>类型检查器不会去检查属性的顺序，只要相应的属性存在并且类型也是对的就可以。</p><h2 id="可选属性">可选属性</h2><p>接口里的属性不全都是必需的。 有些是只在某些条件下存在，或者根本不存在。 可选属性在应用<code>“option bags”</code>模式时很常用，即给函数传入的参数对象中只有部分属性赋值了。</p><p>下面是应用了<code>“option bags”</code>的例子：</p><pre><code class="language-ts">interface SquareConfig {  color?: string;  width?: number;}function createSquare(config: SquareConfig): {color: string; area: number} {  let newSquare = {color: "white", area: 100};  if (config.color) {    newSquare.color = config.color;  }  if (config.width) {    newSquare.area = config.width * config.width;  }  return newSquare;}let mySquare = createSquare({color: "black"});</code></pre><p>带有可选属性的接口与普通的接口定义差不多，只是在可选属性名字定义的后面加一个<code>?</code>符号。</p><p>可选属性的好处之一是可以对可能存在的属性进行预定义，好处之二是可以捕获引用了不存在的属性时的错误。 比如，我们故意将&nbsp;<code>createSquare</code>里的<code>color</code>属性名拼错，就会得到一个错误提示：</p><pre><code class="language-ts">interface SquareConfig {  color?: string;  width?: number;}function createSquare(config: SquareConfig): { color: string; area: number } {  let newSquare = {color: "white", area: 100};  if (config.color) {    // Error: Property 'clor' does not exist on type 'SquareConfig'    newSquare.color = config.clor;  }  if (config.width) {    newSquare.area = config.width * config.width;  }  return newSquare;}let mySquare = createSquare({color: "black"});</code></pre><h2 id="只读属性">只读属性</h2><p>可以在属性名前用&nbsp;<code>readonly</code>来指定只读属性:</p><pre><code class="language-ts">interface Point {    readonly x: number;    readonly y: number;}</code></pre><p>可以通过赋值一个对象字面量来构造一个<code>Point</code>。 赋值后， x 和 y 再也不能被改变了。</p><pre><code class="language-ts">let p1: Point = { x: 10, y: 20 };p1.x = 5; // error!</code></pre><p>TypeScript 具有 ReadonlyArray<t> 类型，它与 Array<t> 相似，只是把所有可变方法去掉了，因此可以确保数组创建后再也不能被修改：</t></t></p><pre><code class="language-ts">let a: number[] = [1, 2, 3, 4];let ro: ReadonlyArray&lt;number&gt; = a;ro[0] = 12; // error!ro.push(5); // error!ro.length = 100; // error!a = ro; // error!</code></pre><p>上面代码的最后一行，可以看到就算把整个<code>ReadonlyArray</code>赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写：</p><pre><code class="language-ts">a = ro as number[];</code></pre><h3 id="readonly--const使用时机">readonly, const使用时机</h3><p>做为变量使用的话用 const ，若做为属性则使用 readonly 。</p><h2 id="函数类型-">函数类型</h2><p>接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外，接口也可以描述函数类型。</p><p>为了使用接口表示函数类型，我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。</p><pre><code class="language-ts">interface SearchFunc {  (source: string, subString: string): boolean;}</code></pre><p>这样定义后，我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量，并将一个同类型的函数赋值给这个变量。</p><pre><code class="language-ts">let mySearch: SearchFunc;mySearch = function(source: string, subString: string) {  let result = source.search(subString);  return result &gt; -1;}</code></pre><p>对于函数类型的类型检查来说，函数的参数名不需要与接口里定义的名字相匹配。 比如，我们使用下面的代码重写上面的例子：</p><pre><code class="language-ts">let mySearch: SearchFunc;mySearch = function(src: string, sub: string): boolean {  let result = src.search(sub);  return result &gt; -1;}</code></pre><p>函数的参数会逐个进行检查，要求对应位置上的参数类型是兼容的。 如果你不想指定类型，TypeScript的类型系统会推断出参数类型，因为函数直接赋值给了&nbsp;<code>SearchFunc</code>类型变量。 函数的返回值类型是通过其返回值推断出来的（此例是&nbsp;<code>false</code>和<code>true</code>）。 如果让这个函数返回数字或字符串，类型检查器会警告我们函数的返回值类型与<code>SearchFunc</code>接口中的定义不匹配。</p><pre><code class="language-ts">let mySearch: SearchFunc;mySearch = function(src, sub) {    let result = src.search(sub);    return result &gt; -1;}</code></pre><h2 id="实现接口">实现接口</h2><p>与C#或Java里接口的基本作用一样，TypeScript也能够用它来明确的强制一个类去符合某种契约。</p><pre><code class="language-ts">interface ClockInterface {    currentTime: Date;}class Clock implements ClockInterface {    currentTime: Date;    constructor(h: number, m: number) { }}</code></pre><p>你也可以在接口中描述一个方法，在类里实现它，如同下面的<code>setTime</code>方法一样：</p><pre><code class="language-ts">interface ClockInterface {    currentTime: Date;    setTime(d: Date);}class Clock implements ClockInterface {    currentTime: Date;    setTime(d: Date) {        this.currentTime = d;    }    constructor(h: number, m: number) { }}</code></pre><p>接口描述了类的公共部分，而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。</p><h2 id="继承接口">继承接口</h2><p>和类一样，接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里，可以更灵活地将接口分割到可重用的模块里。</p><pre><code class="language-ts">interface Shape {    color: string;}interface Square extends Shape {    sideLength: number;}let square = &lt;Square&gt;{};square.color = "blue";square.sideLength = 10;</code></pre><p>一个接口可以继承多个接口，创建出多个接口的合成接口。</p><pre><code class="language-ts">interface Shape {    color: string;}interface PenStroke {    penWidth: number;}interface Square extends Shape, PenStroke {    sideLength: number;}let square = &lt;Square&gt;{};square.color = "blue";square.sideLength = 10;square.penWidth = 5.0;</code></pre><h1 id="类">类</h1><h2 id="类介绍">类介绍</h2><p>传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件，但对于熟悉使用面向对象方式的程序员来讲就有些棘手，因为他们用的是基于类的继承并且对象是由类构建出来的。 从ECMAScript 2015，也就是ECMAScript 6开始，JavaScript程序员将能够使用基于类的面向对象的方式。</p><p>使用TypeScript，我们允许开发者现在就使用这些特性，并且编译后的JavaScript可以在所有主流浏览器和平台上运行，而不需要等到下个JavaScript版本。</p><h3 id="类-">类</h3><p>下面看一个使用类的例子：</p><pre><code class="language-ts">class Greeter {    greeting: string;    constructor(message: string) {        this.greeting = message;    }    greet() {        return "Hello, " + this.greeting;    }}let greeter = new Greeter("world");</code></pre><p>如果你使用过C#或Java，你会对这种语法非常熟悉。 我们声明一个&nbsp;<code>Greeter</code>类。这个类有3个成员：一个叫做&nbsp;<code>greeting</code>的属性，一个构造函数和一个 greet方法。</p><p>你会注意到，我们在引用任何一个类成员的时候都用了&nbsp;<code>this</code>。 它表示我们访问的是类的成员。</p><p>最后一行，我们使用&nbsp;<code>new</code>&nbsp;构造了 Greeter类的一个实例。 它会调用之前定义的构造函数，创建一个Greeter类型的新对象，并执行构造函数初始化它。</p><h2 id="继承">继承</h2><p>在TypeScript里，我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。</p><p>看下面的例子：</p><pre><code class="language-ts">class Animal {    move(distanceInMeters: number = 0) {        console.log(`Animal moved ${distanceInMeters}m.`);    }}class Dog extends Animal {    bark() {        console.log('Woof! Woof!');    }}const dog = new Dog();dog.bark();dog.move(10);dog.bark();</code></pre><p>这个例子展示了最基本的继承：类从基类中继承了属性和方法。 这里， Dog是一个&nbsp;<code>派生类</code>，它派生自<code>Animal 基类</code>，通过&nbsp;<code>extends关键字</code>。 派生类通常被称作 子类，<strong>基类通常被称作 超类</strong>。</p><p>因为 Dog继承了 Animal的功能，因此我们可以创建一个 Dog的实例，它能够&nbsp;<code>bark() 和 move()</code>。</p><p>下面我们来看个更加复杂的例子。</p><pre><code class="language-ts">class Animal {    name: string;    constructor(theName: string) { this.name = theName; }    move(distanceInMeters: number = 0) {        console.log(`${this.name} moved ${distanceInMeters}m.`);    }}class Snake extends Animal {    constructor(name: string) { super(name); }    move(distanceInMeters = 5) {        console.log("Slithering...");        super.move(distanceInMeters);    }}class Horse extends Animal {    constructor(name: string) { super(name); }    move(distanceInMeters = 45) {        console.log("Galloping...");        super.move(distanceInMeters);    }}let sam = new Snake("Sammy the Python");let tom: Animal = new Horse("Tommy the Palomino");sam.move();tom.move(34);</code></pre><p>这个例子展示了一些上面没有提到的特性。 这一次，我们使用 extends关键字创建了 Animal的两个子类：&nbsp;<code>Horse 和 Snake</code>。</p><p>与前一个例子的不同点是，派生类包含了一个构造函数，它必须调用&nbsp;<code>super()</code>，它会执行基类的构造函数。 而且，在构造函数里访问 this的属性之前，我们 一定要调用&nbsp;<code>super()</code>。 这个是TypeScript强制执行的一条重要规则。</p><p>这个例子演示了如何在子类里可以重写父类的方法。 Snake类和 Horse类都创建了 move方法，它们重写了从 Animal继承来的 move方法，使得 move方法根据不同的类而具有不同的功能。 注意，即使tom被声明为 Animal类型，但因为它的值是 Horse，调用 tom.move(34)时，它会调用 Horse里重写的方法：</p><pre><code>Slithering...Sammy the Python moved 5m.Galloping...Tommy the Palomino moved 34m.</code></pre><h2 id="公共-私有与受保护的修饰符">公共，私有与受保护的修饰符</h2><h3 id="public">public</h3><p>在TypeScript里，成员都默认为&nbsp;<code>public</code>。</p><p>你也可以明确的将一个成员标记成 public。 我们可以用下面的方式来重写 Animal类：</p><pre><code class="language-ts">class Animal {    public name: string;    public constructor(theName: string) { this.name = theName; }    public move(distanceInMeters: number) {        console.log(`${this.name} moved ${distanceInMeters}m.`);    }}</code></pre><h2 id="private">private</h2><p>当成员被标记成&nbsp;<code>private</code>时，它就不能在声明它的类的外部访问。比如：</p><pre><code class="language-ts">class Animal {    private name: string;    constructor(theName: string) { this.name = theName; }}new Animal("Cat").name; // 错误: 'name' 是私有的.</code></pre><h2 id="protected">protected</h2><p>protected修饰符与 private修饰符的行为很相似，但有一点不同， protected成员在派生类中仍然可以访问。例如：</p><pre><code class="language-ts">class Person {    protected name: string;    constructor(name: string) { this.name = name; }}class Employee extends Person {    private department: string;    constructor(name: string, department: string) {        super(name)        this.department = department;    }    public getElevatorPitch() {        return `Hello, my name is ${this.name} and I work in ${this.department}.`;    }}let howard = new Employee("Howard", "Sales");console.log(howard.getElevatorPitch());console.log(howard.name); // 错误</code></pre><p>注意，我们不能在 Person类外使用 name，但是我们仍然可以通过 Employee类的实例方法访问，因为 Employee是由 Person派生而来的。</p><p>构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化，但是能被继承。比如，</p><pre><code class="language-ts">class Person {    protected name: string;    protected constructor(theName: string) { this.name = theName; }}// Employee 能够继承 Personclass Employee extends Person {    private department: string;    constructor(name: string, department: string) {        super(name);        this.department = department;    }    public getElevatorPitch() {        return `Hello, my name is ${this.name} and I work in ${this.department}.`;    }}let howard = new Employee("Howard", "Sales");let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.</code></pre><h2 id="readonly修饰符">readonly修饰符</h2><p>你可以使用&nbsp;readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。</p><pre><code class="language-ts">class Octopus {    readonly name: string;    readonly numberOfLegs: number = 8;    constructor (theName: string) {        this.name = theName;    }}let dad = new Octopus("Man with the 8 strong legs");dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.</code></pre><h3 id="参数属性">参数属性</h3><p>在上面的例子中，我们不得不定义一个受保护的成员&nbsp;name和一个构造函数参数&nbsp;theName在&nbsp;Person类里，并且立刻给&nbsp;name和&nbsp;theName赋值。 这种情况经常会遇到。&nbsp;参数属性可以方便地让我们在一个地方定义并初始化一个成员。 下面的例子是对之前&nbsp;Animal类的修改版，使用了参数属性：</p><pre><code class="language-ts">class Animal {    constructor(private name: string) { }    move(distanceInMeters: number) {        console.log(`${this.name} moved ${distanceInMeters}m.`);    }}</code></pre><p>注意看我们是如何舍弃了&nbsp;theName，仅在构造函数里使用&nbsp;private name: string参数来创建和初始化&nbsp;name成员。 我们把声明和赋值合并至一处。</p><p>参数属性通过给构造函数参数添加一个访问限定符来声明。 使用&nbsp;private限定一个参数属性会声明并初始化一个私有成员；对于&nbsp;public和&nbsp;protected来说也是一样。</p><h2 id="存取器">存取器</h2><p>TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。</p><p>下面来看如何把一个简单的类改写成使用&nbsp;get和&nbsp;set。 首先，我们从一个没有使用存取器的例子开始。</p><pre><code class="language-ts">class Employee {    fullName: string;}let employee = new Employee();employee.fullName = "Bob Smith";if (employee.fullName) {    console.log(employee.fullName);}</code></pre><p>我们可以随意的设置&nbsp;fullName，这是非常方便的，但是这也可能会带来麻烦。</p><p>下面这个版本里，我们先检查用户密码是否正确，然后再允许其修改员工信息。 我们把对&nbsp;fullName的直接访问改成了可以检查密码的&nbsp;set方法。 我们也加了一个&nbsp;get方法，让上面的例子仍然可以工作。</p><pre><code class="language-ts">let passcode = "secret passcode";class Employee {    private _fullName: string;    get fullName(): string {        return this._fullName;    }    set fullName(newName: string) {        if (passcode &amp;&amp; passcode == "secret passcode") {            this._fullName = newName;        }        else {            console.log("Error: Unauthorized update of employee!");        }    }}let employee = new Employee();employee.fullName = "Bob Smith";if (employee.fullName) {    alert(employee.fullName);}</code></pre><p>我们可以修改一下密码，来验证一下存取器是否是工作的。当密码不对时，会提示我们没有权限去修改员工。</p><p>对于存取器有下面几点需要注意的：</p><p>首先，存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次，只带有&nbsp;get不带有&nbsp;set的存取器自动被推断为&nbsp;readonly。 这在从代码生成&nbsp;.d.ts文件时是有帮助的，因为利用这个属性的用户会看到不允许够改变它的值。</p><h2 id="静态属性">静态属性</h2><p>到目前为止，我们只讨论了类的实例成员，那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员，这些属性存在于类本身上面而不是类的实例上。 在这个例子里，我们使用static定义&nbsp;origin，因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候，都要在&nbsp;origin前面加上类名。 如同在实例属性上使用&nbsp;this.前缀来访问属性一样，这里我们使用&nbsp;Grid.来访问静态属性。</p><pre><code class="language-ts">class Grid {    static origin = {x: 0, y: 0};    calculateDistanceFromOrigin(point: {x: number; y: number;}) {        let xDist = (point.x - Grid.origin.x);        let yDist = (point.y - Grid.origin.y);        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;    }    constructor (public scale: number) { }}let grid1 = new Grid(1.0);  // 1x scalelet grid2 = new Grid(5.0);  // 5x scaleconsole.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));</code></pre><h2 id="抽象类">抽象类</h2><p>抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口，抽象类可以包含成员的实现细节。&nbsp;abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。</p><pre><code class="language-ts">abstract class Animal {    abstract makeSound(): void;    move(): void {        console.log('roaming the earch...');    }}</code></pre><p>抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而，抽象方法必须包含&nbsp;abstract关键字并且可以包含访问修饰符。</p><pre><code class="language-ts">abstract class Department {    constructor(public name: string) {    }    printName(): void {        console.log('Department name: ' + this.name);    }    abstract printMeeting(): void; // 必须在派生类中实现}class AccountingDepartment extends Department {    constructor() {        super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()    }    printMeeting(): void {        console.log('The Accounting Department meets each Monday at 10am.');    }    generateReports(): void {        console.log('Generating accounting reports...');    }}let department: Department; // 允许创建一个对抽象类型的引用department = new Department(); // 错误: 不能创建一个抽象类的实例department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值department.printName();department.printMeeting();department.generateReports(); // 错误: 方法在声明的抽象类中不存在</code></pre><h1 id="泛型">泛型</h1><h2 id="泛型介绍">泛型介绍</h2><p>软件工程中，我们不仅要创建一致的定义良好的API，同时也要考虑可重用性。 组件不仅能够支持当前的数据类型，同时也能支持未来的数据类型，这在创建大型系统时为你提供了十分灵活的功能。</p><p>在像C#和Java这样的语言中，可以使用泛型来创建可重用的组件，一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。</p><h2 id="非泛型例子">非泛型例子</h2><p>下面来创建 identity函数。 这个函数会返回任何传入它的值。 你可以把这个函数当成是 echo命令。</p><p>非泛型例子1：</p><pre><code class="language-ts">function identity(arg: number): number {    return arg;}</code></pre><p>非泛型例子2: 使用any类型来定义函数</p><pre><code class="language-ts">function identity(arg: any): any {    return arg;}</code></pre><p>使用any类型会导致这个函数可以接收任何类型的arg参数，这样就丢失了一些信息：传入的类型与返回的类型应该是相同的。如果我们传入一个数字，我们只知道任何类型的值都有可能被返回。</p><h2 id="泛型的例子">泛型的例子</h2><p>我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里，我们使用了&nbsp;<strong>类型变量</strong>，它是一种特殊的变量，只用于表示类型而不是值。</p><pre><code class="language-ts">function identity&lt;T&gt;(arg: T): T {    return arg;}</code></pre><p>我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型（比如：number），之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。</p><p>我们把这个版本的identity函数叫做泛型，因为它可以适用于多个类型。 不同于使用 any，它不会丢失信息，像第一个例子那像保持准确性，传入数值类型并返回数值类型。</p><h2 id="泛型类">泛型类</h2><p>泛型类看上去与泛型接口差不多。 泛型类使用（ &lt;&gt;）括起泛型类型，跟在类名后面。</p><pre><code class="language-ts">class GenericNumber&lt;T&gt; {    zeroValue: T;    add: (x: T, y: T) =&gt; T;}let myGenericNumber = new GenericNumber&lt;number&gt;();myGenericNumber.zeroValue = 0;myGenericNumber.add = function(x, y) { return x + y; };</code></pre><p>GenericNumber类的使用是十分直观的，并且你可能已经注意到了，没有什么去限制它只能使用number类型。 也可以使用字符串或其它更复杂的类型。</p><pre><code class="language-ts">let stringNumeric = new GenericNumber&lt;string&gt;();stringNumeric.zeroValue = "";stringNumeric.add = function(x, y) { return x + y; };console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));</code></pre><p>与接口一样，直接把泛型类型放在类后面，可以帮助我们确认类的所有属性都在使用相同的类型。</p><p>我们在类那节说过，类有两部分：静态部分和实例部分。 泛型类指的是实例部分的类型，所以类的静态属性不能使用这个泛型类型。</p><h1 id="枚举-">枚举</h1><p>默认情况下，枚举是基于 0 的，也就是说第一个值是 0，后面的值依次递增。不要担心，当中的每一个值都可以显式指定，只要不出现重复即可，没有被显式指定的值，都会在前一个值的基础上递增。</p><pre><code class="language-ts">enum Color {Red, Green, Blue}let c: Color = Color.Green;  // 1</code></pre><p>或者</p><pre><code class="language-ts">enum Color {Red = 1, Green, Blue = 4}let c: Color = Color.Green;  // 2</code></pre><p>枚举有一个很方便的特性，就是您也可以向枚举传递一个数值，然后获取它对应的名称值。举个例子，如果我们有一个值 2，但是不清楚在 Color 枚举中与之对应的名称是什么，我们就可以通过以下的方式来进行检索：</p><pre><code class="language-ts">enum Color {Red = 1, Green, Blue}let colorName: string = Color[2];  // 'Green'</code></pre><p>但是像上面的这种写法不是太好，因为如果您给定的数值没有与之对应的枚举项，那么结果就是 undefined。所以，如果您想要得到指定枚举项的字符串名称，可以使用类似这样的写法：</p><pre><code class="language-ts">let colorName: string = Color[Color.Green];  // 'Green'</code></pre><h1 id="命名空间">命名空间</h1><p>TypeScript里使用命名空间（之前叫做“内部模块”）来组织你的代码。任何使用 module关键字来声明一个内部模块的地方都应该使用namespace关键字来替换。 这就避免了让新的使用者被相似的名称所迷惑。</p><h3 id="命名空间介绍">命名空间介绍</h3><p>下面的例子里，把所有与验证器相关的类型都放到一个叫做Validation的命名空间里。 因为我们想让这些接口和类在命名空间之外也是可访问的，所以需要使用 export。 相反的，变量 lettersRegexp和numberRegexp是实现的细节，不需要导出，因此它们在命名空间外是不能访问的。 在文件末尾的测试代码里，由于是在命名空间之外访问，因此需要限定类型的名称，比如 Validation.LettersOnlyValidator。</p><pre><code class="language-ts">namespace Validation {    export interface StringValidator {        isAcceptable(s: string): boolean;    }    const lettersRegexp = /^[A-Za-z]+$/;    const numberRegexp = /^[0-9]+$/;    export class LettersOnlyValidator implements StringValidator {        isAcceptable(s: string) {            return lettersRegexp.test(s);        }    }    export class ZipCodeValidator implements StringValidator {        isAcceptable(s: string) {            return s.length === 5 &amp;&amp; numberRegexp.test(s);        }    }}// Some samples to trylet strings = ["Hello", "98052", "101"];// Validators to uselet validators: { [s: string]: Validation.StringValidator; } = {};validators["ZIP code"] = new Validation.ZipCodeValidator();validators["Letters only"] = new Validation.LettersOnlyValidator();// Show whether each string passed each validatorfor (let s of strings) {    for (let name in validators) {        console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);    }}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 23 Apr 2024 15:48:28 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2024-04-21出游计划]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/2024-04-21-chu-you-ji-hua</link>
                    <description>
                            <![CDATA[<p>本次沈阳出游计划旨在让您深入了解沈阳丰富的历史文化和艺术气息。行程包括参观庭澜美术馆，欣赏那里的各类美术作品和艺术展览，随后将参观王明宇公馆旧址，了解沈阳的历史人物和旧时生活环境。接着前往常荫槐公馆，感受沈阳曾经的政治风云。吕氏丹青则为您展示了书法和绘画的艺术魅力。旅程还将包括叁叁文创园，体验文创氛围和时尚气息。最后一站是刘鸿典建筑博物馆，全面了解沈阳的建筑特色和历史变迁。这个全面的沈阳出游计划将使您充分感受到沈阳深厚的文化底蕴和艺术魅力。</p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%87%BA%E6%B8%B8%E8%AE%A1%E5%88%9220240421.png" alt="沈阳出游计划20240421"></p>]]>
                    </description>
                    <pubDate>Thu, 18 Apr 2024 00:22:27 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2024-04-03预选游玩的展览]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/2024-04-03-yu-xuan-you-wan-de-zhan-lan</link>
                    <description>
                            <![CDATA[<h1 id="计划">计划</h1><ul><li class="vditor-task"><input disabled="" type="checkbox">  吕氏丹青</li><li class="vditor-task"><input disabled="" type="checkbox">  超越永恒的金子</li><li class="vditor-task"><input disabled="" type="checkbox">  创世山海经</li><li class="vditor-task"><input disabled="" type="checkbox">  此物非彼物</li><li class="vditor-task"><input disabled="" type="checkbox">  春暖花又开</li></ul><h1 id="吕氏丹青">吕氏丹青</h1><p>展览时间：2024年3月31日至2024年6月30日 9:00am—18:00pm(每周一闭馆)</p><p>展览地点：沈阳市沈河区八纬路48号 Bifrost Gallery X拾物研舍</p><p>费用：49元</p><h1 id="超越永恒的金子">超越永恒的金子</h1><p>展览时间：2024.1.27-2024.4.7</p><p>展览地点：沈阳k11 4层 艺术空间</p><p>费用：单人50元/双人88元</p><h1 id="创世山海经">创世山海经</h1><p>展览时间：2024年4月3日-6月16日 10:00-22:00</p><p>展览地点：沈阳K11购物艺术中心L2展厅</p><p>费用：</p><p>早鸟通票58元(原价78元)<br>早鸟儿童票 28元（原价78元）<br>早鸟优待票38元</p><p>开展后：<br>单人票78元 儿童票 38元（原价78元）<br>亲子票99元 优待票48元<br>双人票128元 三人票178元</p><h1 id="此物非彼物">此物非彼物</h1><p>展览时间：2024.03.29-2024.04.29   10:00-17:00</p><p>展览地点：沈阳市沈河区正阳街192号庭澜美术馆</p><p>费用：免费</p><h1 id="春暖花又开">春暖花又开</h1><p>展览时间：2024年3月20日——4月20日</p><p>展览地点：沈阳美术馆1号厅、3号厅</p><p>费用：免费</p>]]>
                    </description>
                    <pubDate>Wed, 03 Apr 2024 14:22:17 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2024-03-30出行项目规划]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/2024-03-30-chu-xing-xiang-mu-gui-hua</link>
                    <description>
                            <![CDATA[<p>1、中国沈阳审判日本战犯法庭旧址陈列馆<br>9:00 ~ 16:00<br>2、北塔（护国法轮寺）<br>8:30 ~ 15:00<br>步行1.5公里，20分钟，公交车至少30分钟（还需要走1公里左右）<br>3、东塔（护国永光寺，46分钟，4元）<br>9:00 ~ 16:00<br>10号线（长安路D口）-&gt; 237（东塔，8分钟）<br>4、南塔（护国广慈寺，50分钟，4元）<br>8:00 ~ 17:00<br>237（大南门西，8分钟）-&gt;环路（南塔，5分钟）<br>5、西塔（护国延寿寺）（58分钟，2元）<br>20:00 ~ 24:00,12:00 ~ 15:00<br>282（西塔东站，8分钟）<br>6、奉天工场文化创意园（57分钟，4元）<br>10:00 ~ 21:00<br>243（北二路兴工街，8分钟）-&gt;235（北二路启工街，15分钟）</p>]]>
                    </description>
                    <pubDate>Fri, 29 Mar 2024 21:20:58 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[win电脑安装绿色版MySQL8]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/win-dian-nao-an-zhuang-lv-se-ban-mysql8</link>
                    <description>
                            <![CDATA[<h1 id="一-下载压缩包">一、下载压缩包</h1><p>下载mysql server的zip文件，地址：<a href="https://dev.mysql.com/downloads/file/?id=526085">Windows (x86, 64-bit), ZIP Archive</a></p><p>解压后：</p><p><img src="https://img.huangge1199.cn/blog/win-dian-nao-an-zhuang-lv-se-ban-mysql8/2024-03-19-13-28-15-image.png" alt=""></p><h1 id="二-创建配置文件-可忽略-">二、创建配置文件（可忽略）</h1><p>配置文件可存放位置及名称：</p><ul><li>C:\WINDOWS\my.ini</li><li>C:\WINDOWS\my.cnf</li><li>C:\my.ini</li><li>C:\my.cnf</li><li>解压目录的根目录下（mysql-8.3.0-winx64）\my.ini</li><li>解压目录的根目录下（mysql-8.3.0-winx64）\my.cnf</li></ul><h1 id="三-初始化数据库">三、初始化数据库</h1><p>以管理员身份运行<code>cmd</code>，进入到<code>bin</code>目录，运行下面的命令创建mysql默认的数据库，并创建一个root账号，空密码</p><pre><code class="language-bash">mysqld --initialize-insecure</code></pre><h1 id="四-启动MySQL服务">四、启动MySQL服务</h1><p>我使用的是安装到服务的方式，执行下面的命令</p><pre><code class="language-bash">mysqld --install-manual</code></pre><p><img src="https://img.huangge1199.cn/blog/win-dian-nao-an-zhuang-lv-se-ban-mysql8/2024-03-19-13-43-14-image.png" alt=""></p><p>默认创建的服务名称为MySQL，然后在服务中启动</p><p><img src="https://img.huangge1199.cn/blog/win-dian-nao-an-zhuang-lv-se-ban-mysql8/2024-03-19-13-44-34-image.png" alt=""></p><p>也可以直接运行一下命令</p><pre><code class="language-bash">mysqld</code></pre><p>这是最简单的方式了，但是无法安装到服务中，其他详细的可参看帮助说明</p><pre><code class="language-bash">mysqld --verbose --help | more</code></pre><p><img src="https://img.huangge1199.cn/blog/win-dian-nao-an-zhuang-lv-se-ban-mysql8/2024-03-19-13-39-49-image.png" alt=""></p><p>红色框内的是安装相关的命令，蓝色框内是移除服务相关的命令</p><h1 id="五-测试">五、测试</h1><p>运行命令确认MySQL能够使用</p><pre><code class="language-bash">mysql -uroot</code></pre><p><img src="https://img.huangge1199.cn/blog/win-dian-nao-an-zhuang-lv-se-ban-mysql8/2024-03-19-13-47-00-image.png" alt=""></p><h1 id="六-修改root密码">六、修改root密码</h1><p>由于前面的方式，root用户没有密码，需要添加个密码，MySQL进入后，执行下面的命令给root设置密码</p><pre><code class="language-bash">use mysql；ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '新密码';FLUSH PRIVILEGES;</code></pre>]]>
                    </description>
                    <pubDate>Tue, 19 Mar 2024 13:22:04 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Git操作指南：子模块、用户名修改和Subtree]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/git-cao-zuo-zhi-nan--zi-mo-kuai--yong-hu-ming-xiu-gai-he-subtree</link>
                    <description>
                            <![CDATA[<h1 id="引言">引言</h1><p>在软件开发中，版本控制是一个至关重要的环节。Git 作为目前最流行的版本控制工具之一，提供了丰富的功能和灵活的操作方式。本文将介绍一些常用的 Git 操作，包括管理子模块、修改用户名、使用 Git Subtree 合并项目以及其他一些常见操作。</p><h1 id="一-引用子模块">一、引用子模块</h1><p><code>git submodule</code>是一个用于将其他两个 Git 仓库嵌入到一个主仓库中。这样做可以使主仓库包含其他两个仓库的内容，并能够管理它们的版本和更新。以下是将两个其他仓库添加为子模块到主仓库的基本步骤：</p><h2 id="1-初始化主仓库">1、初始化主仓库</h2><pre><code class="language-bash">mkdir main_projectcd main_projectgit init</code></pre><h2 id="2-添加子模块">2、添加子模块</h2><p>使用 <code>git submodule add</code> 命令将其他仓库添加为子模块到主仓库中。</p><pre><code class="language-bash">git submodule add &lt;URL_of_repository1&gt; repository1_foldergit submodule add &lt;URL_of_repository2&gt; repository2_folder</code></pre><h2 id="3-提交更改">3、提交更改</h2><pre><code class="language-bash">git commit -m "Add submodules repository1 and repository2"</code></pre><p>现在，主仓库包含了两个子模块，它们的内容在 <code>repository1_folder</code> 和 <code>repository2_folder</code> 中。</p><p>当你克隆主仓库时，子模块的内容并不会自动下载。你需要执行以下命令来初始化和更新子模块：</p><pre><code class="language-bash">git submodule update --init --recursive</code></pre><p>这会初始化并拉取子模块的内容。之后，你可以像管理普通的 Git 仓库一样来管理这些子模块，例如切换到不同的分支或提交更改。</p><p>需要注意的是，子模块在主仓库中只是一个指向子仓库的引用，它不会把子仓库的内容直接嵌入到主仓库中。这意味着你可以独立地管理每个子仓库的版本和更新。</p><p>在主仓库中，如果需要查看子模块的提交记录，可以使用下面的命令：</p><pre><code class="language-bash">git log --recurse-submodules</code></pre><h1 id="二-删除引用的子模块">二、删除引用的子模块</h1><p>如果需要删除子模块，你需要执行以下步骤：</p><h2 id="1-移除子模块的配置">1、移除子模块的配置</h2><p>使用 <code>git submodule deinit</code> 命令来从 <code>.gitmodules</code> 文件中移除子模块的配置信息，并删除 <code>.git/modules/&lt;submodule_folder&gt;</code> 文件夹中的子模块内容。例如，假设子模块的文件夹名为 <code>submodule_folder</code>：</p><pre><code class="language-bash">git submodule deinit -f &lt;submodule_folder&gt;</code></pre><h2 id="2--删除子模块的文件夹">2、 删除子模块的文件夹</h2><p>删除主项目中包含子模块内容的文件夹。在上面的例子中，删除名为 <code>&lt;submodule_folder&gt;</code> 的文件夹：</p><pre><code class="language-bash">git rm -f &lt;submodule_folder&gt;</code></pre><h2 id="3-提交更改-">3、提交更改</h2><pre><code class="language-bash">git commit -m "Remove submodule &lt;submodule_folder&gt;"git push</code></pre><h1 id="三-修改用户名">三、修改用户名</h1><p>要修改 Git 中的用户名，你需要执行以下步骤：</p><h2 id="1-全局修改用户名">1、全局修改用户名</h2><p>使用以下命令设置全局用户名：</p><pre><code class="language-bash">git config --global user.name "Your New Username"</code></pre><p>替换 <code>"Your New Username"</code> 为你想要设置的新用户名。</p><h2 id="2-针对单个仓库修改用户名-可选-">2、针对单个仓库修改用户名（可选）</h2><p>如果你只想在特定的仓库中修改用户名，而不是全局修改，可以在该仓库中执行以下命令：</p><pre><code class="language-bash">git config user.name "Your New Username"</code></pre><h2 id="3-验证修改是否成功">3、验证修改是否成功</h2><p>你可以运行以下命令来验证修改是否成功：</p><pre><code class="language-bash">git config user.name</code></pre><p>这会显示当前配置的用户名，确保它已经更新为你想要的新用户名。</p><p>通过执行上述步骤，你就可以修改 Git 中的用户名了。</p><h1 id="四-整合子模块">四、整合子模块</h1><p>Git Subtree 是一个用于合并不同 Git 仓库的工具，它允许将一个仓库的部分历史合并到另一个仓库中，而且可以保留提交记录。</p><p>以下是将子模块项目转移到主项目中并保存子模块项目的提交记录的基本步骤：</p><h2 id="1-添加子模块内容到主项目中">1、添加子模块内容到主项目中</h2><pre><code class="language-bash">git subtree add --prefix=&lt;submodule_folder&gt; &lt;submodule_repo_url&gt; &lt;submodule_branch&gt; --squash</code></pre><p>这个命令将子模块的内容合并到主项目中的指定文件夹 <code>&lt;submodule_folder&gt;</code> 中。<code>--squash</code> 选项用于将子模块的历史压缩成一个新的提交。</p><h2 id="2-提交更改到主项目">2、提交更改到主项目</h2><pre><code class="language-bash">git commit -m "Merge submodule repository into main project"</code></pre><p>这个提交将包含所有合并的子模块内容。</p><h2 id="3-在以后的更新中同步子模块内容-可选-">3、在以后的更新中同步子模块内容（可选）</h2><p>如果子模块的内容在原始仓库中发生了变化，你可能想要将这些变化同步到主项目中。你可以使用以下命令：</p><pre><code class="language-bash">git subtree pull --prefix=&lt;submodule_folder&gt; &lt;submodule_repo_url&gt; &lt;submodule_branch&gt; --squash</code></pre><p>这会将子模块的最新更改合并到主项目中。</p><p>使用 <code>git subtree</code> 的主要优点是它可以保留子模块项目的提交历史，并将其合并到主项目的提交历史中。这样可以更清晰地追踪子模块项目的变化，并且可以保持主项目的整洁性。</p><h1 id="五-其他常见操作">五、其他常见操作</h1><p>除了上述操作之外，还有一些其他常见的 Git 操作：</p><ul><li>提交代码：使用 <code>git commit</code> 命令将修改提交到本地仓库。</li><li>推送代码：使用 <code>git push</code> 命令将本地仓库中的修改推送到远程仓库。</li><li>合并代码：使用 <code>git merge</code> 命令将不同分支的代码合并到当前分支。</li><li>创建分支：使用 <code>git branch</code> 命令创建新的分支。</li><li>拉取代码：使用 <code>git pull</code> 命令从远程仓库拉取最新的代码。</li></ul><h1 id="结语">结语</h1><p>本文介绍了一些常见的 Git 操作，包括管理子模块、修改用户名、使用 Git Subtree 合并项目以及其他一些常用操作。通过熟练掌握这些操作，你将能够更加高效地使用 Git 进行版本控制，并且更好地管理你的项目代码。</p><pre><code></code></pre>]]>
                    </description>
                    <pubDate>Wed, 13 Mar 2024 16:57:03 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[docker-compose部署单机版nacos]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/docker-compose-bu-shu-dan-ji-ban-nacos</link>
                    <description>
                            <![CDATA[<h1 id="nacos数据库建表语句">nacos数据库建表语句</h1><pre><code class="language-sql">/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *//******************************************//*   数据库全名 = nacos_config   *//*   表名称 = config_info   *//******************************************/CREATE TABLE `config_info` (                               `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',                               `data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',                               `group_id` VARCHAR(255) DEFAULT NULL,                               `content` LONGTEXT NOT NULL COMMENT 'content',                               `md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',                               `gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',                               `gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',                               `src_user` TEXT COMMENT 'source user',                               `src_ip` VARCHAR(50) DEFAULT NULL COMMENT 'source ip',                               `app_name` VARCHAR(128) DEFAULT NULL,                               `tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',                               `c_desc` VARCHAR(256) DEFAULT NULL,                               `c_use` VARCHAR(64) DEFAULT NULL,                               `effect` VARCHAR(64) DEFAULT NULL,                               `type` VARCHAR(64) DEFAULT NULL,                               `c_schema` TEXT,                               `encrypted_data_key` TEXT NOT NULL COMMENT '秘钥',                               PRIMARY KEY (`id`),                               UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';/******************************************//*   数据库全名 = nacos_config   *//*   表名称 = config_info_aggr   *//******************************************/CREATE TABLE `config_info_aggr` (                                    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',                                    `data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',                                    `group_id` VARCHAR(255) NOT NULL COMMENT 'group_id',                                    `datum_id` VARCHAR(255) NOT NULL COMMENT 'datum_id',                                    `content` LONGTEXT NOT NULL COMMENT '内容',                                    `gmt_modified` DATETIME NOT NULL COMMENT '修改时间',                                    `app_name` VARCHAR(128) DEFAULT NULL,                                    `tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',                                    PRIMARY KEY (`id`),                                    UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';/******************************************//*   数据库全名 = nacos_config   *//*   表名称 = config_info_beta   *//******************************************/CREATE TABLE `config_info_beta` (                                    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',                                    `data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',                                    `group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',                                    `app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',                                    `content` LONGTEXT NOT NULL COMMENT 'content',                                    `beta_ips` VARCHAR(1024) DEFAULT NULL COMMENT 'betaIps',                                    `md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',                                    `gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',                                    `gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',                                    `src_user` TEXT COMMENT 'source user',                                    `src_ip` VARCHAR(50) DEFAULT NULL COMMENT 'source ip',                                    `tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',                                    `encrypted_data_key` TEXT NOT NULL COMMENT '秘钥',                                    PRIMARY KEY (`id`),                                    UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';/******************************************//*   数据库全名 = nacos_config   *//*   表名称 = config_info_tag   *//******************************************/CREATE TABLE `config_info_tag` (                                   `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',                                   `data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',                                   `group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',                                   `tenant_id` VARCHAR(128) DEFAULT '' COMMENT 'tenant_id',                                   `tag_id` VARCHAR(128) NOT NULL COMMENT 'tag_id',                                   `app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',                                   `content` LONGTEXT NOT NULL COMMENT 'content',                                   `md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',                                   `gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',                                   `gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',                                   `src_user` TEXT COMMENT 'source user',                                   `src_ip` VARCHAR(50) DEFAULT NULL COMMENT 'source ip',                                   PRIMARY KEY (`id`),                                   UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';/******************************************//*   数据库全名 = nacos_config   *//*   表名称 = config_tags_relation   *//******************************************/CREATE TABLE `config_tags_relation` (                                        `id` BIGINT(20) NOT NULL COMMENT 'id',                                        `tag_name` VARCHAR(128) NOT NULL COMMENT 'tag_name',                                        `tag_type` VARCHAR(64) DEFAULT NULL COMMENT 'tag_type',                                        `data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',                                        `group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',                                        `tenant_id` VARCHAR(128) DEFAULT '' COMMENT 'tenant_id',                                        `nid` BIGINT(20) NOT NULL AUTO_INCREMENT,                                        PRIMARY KEY (`nid`),                                        UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),                                        KEY `idx_tenant_id` (`tenant_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';/******************************************//*   数据库全名 = nacos_config   *//*   表名称 = group_capacity   *//******************************************/CREATE TABLE `group_capacity` (                                  `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',                                  `group_id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Group ID，空字符表示整个集群',                                  `quota` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '配额，0表示使用默认值',                                  `usage` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '使用量',                                  `max_size` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '单个配置大小上限，单位为字节，0表示使用默认值',                                  `max_aggr_count` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数，，0表示使用默认值',                                  `max_aggr_size` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限，单位为字节，0表示使用默认值',                                  `max_history_count` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',                                  `gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',                                  `gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',                                  PRIMARY KEY (`id`),                                  UNIQUE KEY `uk_group_id` (`group_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';/******************************************//*   数据库全名 = nacos_config   *//*   表名称 = his_config_info   *//******************************************/CREATE TABLE `his_config_info` (                                   `id` BIGINT(64) UNSIGNED NOT NULL,                                   `nid` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,                                   `data_id` VARCHAR(255) NOT NULL,                                   `group_id` VARCHAR(128) NOT NULL,                                   `app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',                                   `content` LONGTEXT NOT NULL,                                   `md5` VARCHAR(32) DEFAULT NULL,                                   `gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,                                   `gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,                                   `src_user` TEXT,                                   `src_ip` VARCHAR(50) DEFAULT NULL,                                   `op_type` CHAR(10) DEFAULT NULL,                                   `tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',                                   `encrypted_data_key` TEXT NOT NULL COMMENT '秘钥',                                   PRIMARY KEY (`nid`),                                   KEY `idx_gmt_create` (`gmt_create`),                                   KEY `idx_gmt_modified` (`gmt_modified`),                                   KEY `idx_did` (`data_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';/******************************************//*   数据库全名 = nacos_config   *//*   表名称 = tenant_capacity   *//******************************************/CREATE TABLE `tenant_capacity` (                                   `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',                                   `tenant_id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',                                   `quota` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '配额，0表示使用默认值',                                   `usage` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '使用量',                                   `max_size` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '单个配置大小上限，单位为字节，0表示使用默认值',                                   `max_aggr_count` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',                                   `max_aggr_size` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限，单位为字节，0表示使用默认值',                                   `max_history_count` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',                                   `gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',                                   `gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',                                   PRIMARY KEY (`id`),                                   UNIQUE KEY `uk_tenant_id` (`tenant_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';CREATE TABLE `tenant_info` (                               `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',                               `kp` VARCHAR(128) NOT NULL COMMENT 'kp',                               `tenant_id` VARCHAR(128) DEFAULT '' COMMENT 'tenant_id',                               `tenant_name` VARCHAR(128) DEFAULT '' COMMENT 'tenant_name',                               `tenant_desc` VARCHAR(256) DEFAULT NULL COMMENT 'tenant_desc',                               `create_source` VARCHAR(32) DEFAULT NULL COMMENT 'create_source',                               `gmt_create` BIGINT(20) NOT NULL COMMENT '创建时间',                               `gmt_modified` BIGINT(20) NOT NULL COMMENT '修改时间',                               PRIMARY KEY (`id`),                               UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),                               KEY `idx_tenant_id` (`tenant_id`)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';CREATE TABLE `users` (                         `username` VARCHAR(50) NOT NULL PRIMARY KEY,                         `password` VARCHAR(500) NOT NULL,                         `enabled` BOOLEAN NOT NULL);CREATE TABLE `roles` (                         `username` VARCHAR(50) NOT NULL,                         `role` VARCHAR(50) NOT NULL,                         UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE);CREATE TABLE `permissions` (                               `role` VARCHAR(50) NOT NULL,                               `resource` VARCHAR(255) NOT NULL,                               `action` VARCHAR(8) NOT NULL,                               UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE);INSERT INTO users (username, PASSWORD, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');</code></pre><h1 id="docker-compose-yaml内容如下-">docker-compose.yaml内容如下：</h1><pre><code class="language-yaml">version: "3.0"services:  nacos:    image: nacos/nacos-server:2.0.3    container_name: nacos    volumes:      - ./logs/:/home/nacos/logs    ports:      - "8848:8848"      - "9848:9848"    environment:      MODE: standalone      PREFER_HOST_MODE: hostname      SPRING_DATASOURCE_PLATFORM: mysql      MYSQL_SERVICE_HOST: 数据库IP地址（例：127.0.0.1）      MYSQL_SERVICE_DB_NAME: 数据库名称      MYSQL_SERVICE_PORT: 数据库端口号      MYSQL_SERVICE_USER: 数据库连接用户名      MYSQL_SERVICE_PASSWORD: 数据库连接密码    restart: always</code></pre><h1 id="web登录页">web登录页</h1><p>http://IP:8848/nacos<br>用户名和密码都是nacos</p>]]>
                    </description>
                    <pubDate>Fri, 08 Mar 2024 16:53:08 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Map类方法整理（jdk8）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/map-lei-fang-fa-zheng-li-jdk8</link>
                    <description>
                            <![CDATA[<h1 id="前言">前言</h1><p>今天在查看力扣周赛385题解时，发现了几个我平时没注意的map方法，看了jdk相关的源码，感觉很巧妙，可以帮我节省代码，于是乎顺带着整个Map类的方法都过了一遍，下面是我看后整理的内容。</p><p>Map类中包括了以下方法：</p><ul><li><a href="#clear">clear()</a></li><li><a href="#computekbifunctionkvv">compute(K,BiFunction</a></li><li><a href="#computeifabsentkfunctionkv">computeIfAbsent(K,Function</a></li><li><a href="#computeifpresentkbifunctionkvv">computeIfPresent(K,BiFunction</a></li><li><a href="#containskeyobject">containsKey(Object)</a></li><li><a href="#containsvalueobject">containsValue(Object)</a></li><li><a href="#entryset">entrySet()</a></li><li><a href="#equalsobject">equals(Object)</a></li><li><a href="#foreachbiconsumerkv">forEach(BiConsumer</a></li><li><a href="#getobject">get(Object)</a></li><li><a href="#getordefaultobject">getOrDefault(Object, V)</a></li><li><a href="#hashcode">hashCode()</a></li><li><a href="#isempty">isEmpty()</a></li><li><a href="#keyset">keySet()</a></li><li><a href="#mergekvbifunctionvvv">merge(K,V,BiFunction</a></li><li><a href="#putkv">put(K,V)</a></li><li><a href="#putallmapkv">putAll(Map</a></li><li><a href="#putifabsentkv">putIfAbsent(K,V)</a></li><li><a href="#removeobject">remove(Object)</a></li><li><a href="#removeobjectobject">remove(Object,Object)</a></li><li><a href="#replacekvv">replace(K,V,V)</a></li><li><a href="#replacekv">replace(K,V)</a></li><li><a href="#replaceallbifunctionkvv">replaceAll(BiFunction</a></li><li><a href="#size">size()</a></li><li><a href="#values">values()</a></li></ul><h1 id="方法详解">方法详解</h1><h2 id="clear--"><a id="clear">clear()</a></h2><p>源码：</p><pre><code class="language-java">/** * Removes all of the mappings from this map (optional operation). * The map will be empty after this call returns. * * @throws UnsupportedOperationException if the &lt;tt&gt;clear&lt;/tt&gt; operation *         is not supported by this map */void clear();</code></pre><p>功能：移除Map中所有的键值对。</p><h2 id="compute-K-BiFunction-K-V-V--"><a id="computekbifunctionkvv">compute(K,BiFunction&lt;K,V,V&gt;)</a></h2><p>源码：</p><pre><code class="language-java">/** * Attempts to compute a mapping for the specified key and its current * mapped value (or {@code null} if there is no current mapping). For * example, to either create or append a {@code String} msg to a value * mapping: * * &lt;pre&gt; {@code * map.compute(key, (k, v) -&gt; (v == null) ? msg : v.concat(msg))}&lt;/pre&gt; * (Method {@link #merge merge()} is often simpler to use for such purposes.) * * &lt;p&gt;If the function returns {@code null}, the mapping is removed (or * remains absent if initially absent).  If the function itself throws an * (unchecked) exception, the exception is rethrown, and the current mapping * is left unchanged. * * @implSpec * The default implementation is equivalent to performing the following * steps for this {@code map}, then returning the current value or * {@code null} if absent: * * &lt;pre&gt; {@code * V oldValue = map.get(key); * V newValue = remappingFunction.apply(key, oldValue); * if (oldValue != null ) { *    if (newValue != null) *       map.put(key, newValue); *    else *       map.remove(key); * } else { *    if (newValue != null) *       map.put(key, newValue); *    else *       return null; * } * }&lt;/pre&gt; * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. In particular, all implementations of * subinterface {@link java.util.concurrent.ConcurrentMap} must document * whether the function is applied once atomically only if the value is not * present. * * @param key key with which the specified value is to be associated * @param remappingFunction the function to compute a value * @return the new value associated with the specified key, or null if none * @throws NullPointerException if the specified key is null and *         this map does not support null keys, or the *         remappingFunction is null * @throws UnsupportedOperationException if the {@code put} operation *         is not supported by this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/ * @throws ClassCastException if the class of the specified key or value *         prevents it from being stored in this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/ * @since 1.8 */default V compute(K key,        BiFunction&lt;? super K, ? super V, ? extends V&gt; remappingFunction) {    Objects.requireNonNull(remappingFunction);    V oldValue = get(key);    V newValue = remappingFunction.apply(key, oldValue);    if (newValue == null) {        // delete mapping        if (oldValue != null || containsKey(key)) {            // something to remove            remove(key);            return null;        } else {            // nothing to do. Leave things as they were.            return null;        }    } else {        // add or replace old mapping        put(key, newValue);        return newValue;    }}</code></pre><p>功能：</p><ul><li>根据指定的键和重新映射函数计算新值，并将新值存储到Map中。</li><li>如果键存在，则根据提供的函数计算新值并更新到Map中，然后返回新值；如果键不存在，则不执行任何操作。</li></ul><h2 id="computeIfAbsent-K-Function-K-V--"><a id="computeifabsentkfunctionkv">computeIfAbsent(K,Function&lt;K,V&gt;)</a></h2><p>源码：</p><pre><code class="language-java">/** * If the specified key is not already associated with a value (or is mapped * to {@code null}), attempts to compute its value using the given mapping * function and enters it into this map unless {@code null}. * * &lt;p&gt;If the function returns {@code null} no mapping is recorded. If * the function itself throws an (unchecked) exception, the * exception is rethrown, and no mapping is recorded.  The most * common usage is to construct a new object serving as an initial * mapped value or memoized result, as in: * * &lt;pre&gt; {@code * map.computeIfAbsent(key, k -&gt; new Value(f(k))); * }&lt;/pre&gt; * * &lt;p&gt;Or to implement a multi-value map, {@code Map&lt;K,Collection&lt;V&gt;&gt;}, * supporting multiple values per key: * * &lt;pre&gt; {@code * map.computeIfAbsent(key, k -&gt; new HashSet&lt;V&gt;()).add(v); * }&lt;/pre&gt; * * * @implSpec * The default implementation is equivalent to the following steps for this * {@code map}, then returning the current value or {@code null} if now * absent: * * &lt;pre&gt; {@code * if (map.get(key) == null) { *     V newValue = mappingFunction.apply(key); *     if (newValue != null) *         map.put(key, newValue); * } * }&lt;/pre&gt; * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. In particular, all implementations of * subinterface {@link java.util.concurrent.ConcurrentMap} must document * whether the function is applied once atomically only if the value is not * present. * * @param key key with which the specified value is to be associated * @param mappingFunction the function to compute a value * @return the current (existing or computed) value associated with *         the specified key, or null if the computed value is null * @throws NullPointerException if the specified key is null and *         this map does not support null keys, or the mappingFunction *         is null * @throws UnsupportedOperationException if the {@code put} operation *         is not supported by this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws ClassCastException if the class of the specified key or value *         prevents it from being stored in this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @since 1.8 */default V computeIfAbsent(K key,        Function&lt;? super K, ? extends V&gt; mappingFunction) {    Objects.requireNonNull(mappingFunction);    V v;    if ((v = get(key)) == null) {        V newValue;        if ((newValue = mappingFunction.apply(key)) != null) {            put(key, newValue);            return newValue;        }    }    return v;}</code></pre><p>功能：</p><ul><li>如果指定键不存在，则根据指定的映射函数计算并将结果存储到Map中；如果键已经存在，则不执行任何操作。</li><li>这个方法允许根据键的不存在来动态计算值，并将计算结果存储到Map中。</li></ul><h2 id="computeIfPresent-K-BiFunction-K-V-V--"><a id="computeifpresentkbifunctionkvv">computeIfPresent(K,BiFunction&lt;K,V,V&gt;)</a></h2><p>源码：</p><pre><code class="language-java">/** * If the value for the specified key is present and non-null, attempts to * compute a new mapping given the key and its current mapped value. * * &lt;p&gt;If the function returns {@code null}, the mapping is removed.  If the * function itself throws an (unchecked) exception, the exception is * rethrown, and the current mapping is left unchanged.* * @implSpec * The default implementation is equivalent to performing the following * steps for this {@code map}, then returning the current value or * {@code null} if now absent: * * &lt;pre&gt; {@code * if (map.get(key) != null) { *     V oldValue = map.get(key); *     V newValue = remappingFunction.apply(key, oldValue); *     if (newValue != null) *         map.put(key, newValue); *     else *         map.remove(key); * } * }&lt;/pre&gt; * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. In particular, all implementations of * subinterface {@link java.util.concurrent.ConcurrentMap} must document * whether the function is applied once atomically only if the value is not * present. * * @param key key with which the specified value is to be associated * @param remappingFunction the function to compute a value * @return the new value associated with the specified key, or null if none * @throws NullPointerException if the specified key is null and *         this map does not support null keys, or the *         remappingFunction is null * @throws UnsupportedOperationException if the {@code put} operation *         is not supported by this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws ClassCastException if the class of the specified key or value *         prevents it from being stored in this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @since 1.8 */default V computeIfPresent(K key,        BiFunction&lt;? super K, ? super V, ? extends V&gt; remappingFunction) {    Objects.requireNonNull(remappingFunction);    V oldValue;    if ((oldValue = get(key)) != null) {        V newValue = remappingFunction.apply(key, oldValue);        if (newValue != null) {            put(key, newValue);            return newValue;        } else {            remove(key);            return null;        }    } else {        return null;    }}</code></pre><p>功能：</p><ul><li>如果指定键存在，则根据提供的重新映射函数计算新值，并将新值存储到Map中；如果键不存在，则不执行任何操作。</li><li>这个方法允许在键存在时根据原始值计算新值，并更新到Map中。</li></ul><h2 id="containsKey-Object-"><a id="containskeyobject">containsKey(Object)</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns &lt;tt&gt;true&lt;/tt&gt; if this map contains a mapping for the specified * key.  More formally, returns &lt;tt&gt;true&lt;/tt&gt; if and only if * this map contains a mapping for a key &lt;tt&gt;k&lt;/tt&gt; such that * &lt;tt&gt;(key==null ? k==null : key.equals(k))&lt;/tt&gt;.  (There can be * at most one such mapping.) * * @param key key whose presence in this map is to be tested * @return &lt;tt&gt;true&lt;/tt&gt; if this map contains a mapping for the specified *         key * @throws ClassCastException if the key is of an inappropriate type for *         this map * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified key is null and this map *         does not permit null keys * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) */boolean containsKey(Object key);</code></pre><p>功能：判断Map中是否包含指定的键。</p><h2 id="containsValue-Object-"><a id="containsvalueobject">containsValue(Object)</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns &lt;tt&gt;true&lt;/tt&gt; if this map maps one or more keys to the * specified value.  More formally, returns &lt;tt&gt;true&lt;/tt&gt; if and only if * this map contains at least one mapping to a value &lt;tt&gt;v&lt;/tt&gt; such that * &lt;tt&gt;(value==null ? v==null : value.equals(v))&lt;/tt&gt;.  This operation * will probably require time linear in the map size for most * implementations of the &lt;tt&gt;Map&lt;/tt&gt; interface. * * @param value value whose presence in this map is to be tested * @return &lt;tt&gt;true&lt;/tt&gt; if this map maps one or more keys to the *         specified value * @throws ClassCastException if the value is of an inappropriate type for *         this map * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified value is null and this *         map does not permit null values * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) */boolean containsValue(Object value);</code></pre><p>功能：判断Map中是否包含指定的值。</p><h2 id="entrySet--"><a id="entrySet">entrySet()</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns a {@link Set} view of the mappings contained in this map. * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa.  If the map is modified * while an iteration over the set is in progress (except through * the iterator's own &lt;tt&gt;remove&lt;/tt&gt; operation, or through the * &lt;tt&gt;setValue&lt;/tt&gt; operation on a map entry returned by the * iterator) the results of the iteration are undefined.  The set * supports element removal, which removes the corresponding * mapping from the map, via the &lt;tt&gt;Iterator.remove&lt;/tt&gt;, * &lt;tt&gt;Set.remove&lt;/tt&gt;, &lt;tt&gt;removeAll&lt;/tt&gt;, &lt;tt&gt;retainAll&lt;/tt&gt; and * &lt;tt&gt;clear&lt;/tt&gt; operations.  It does not support the * &lt;tt&gt;add&lt;/tt&gt; or &lt;tt&gt;addAll&lt;/tt&gt; operations. * * @return a set view of the mappings contained in this map */Set&lt;Map.Entry&lt;K, V&gt;&gt; entrySet();</code></pre><p>功能：返回一个包含Map中所有键值对的Set集合。</p><h2 id="equals-Object-"><a id="equalsobject">equals(Object)</a></h2><p>源码：</p><pre><code class="language-java">/** * Compares the specified object with this map for equality.  Returns * &lt;tt&gt;true&lt;/tt&gt; if the given object is also a map and the two maps * represent the same mappings.  More formally, two maps &lt;tt&gt;m1&lt;/tt&gt; and * &lt;tt&gt;m2&lt;/tt&gt; represent the same mappings if * &lt;tt&gt;m1.entrySet().equals(m2.entrySet())&lt;/tt&gt;.  This ensures that the * &lt;tt&gt;equals&lt;/tt&gt; method works properly across different implementations * of the &lt;tt&gt;Map&lt;/tt&gt; interface. * * @param o object to be compared for equality with this map * @return &lt;tt&gt;true&lt;/tt&gt; if the specified object is equal to this map */boolean equals(Object o);</code></pre><p>功能：比较两个 <code>Map</code> 对象是否相等。两个 <code>Map</code> 相等的条件是：</p><ol><li>两个 <code>Map</code> 对象具有相同的键值对数量。</li><li>对于每个键，两个 <code>Map</code> 对象的键值必须相等。</li></ol><h2 id="forEach-BiConsumer-K-V--"><a id="foreachbiconsumerkv">forEach(BiConsumer&lt;K,V&gt;)</a></h2><p>源码：</p><pre><code class="language-java">/** * Performs the given action for each entry in this map until all entries * have been processed or the action throws an exception.   Unless * otherwise specified by the implementing class, actions are performed in * the order of entry set iteration (if an iteration order is specified.) * Exceptions thrown by the action are relayed to the caller. * * @implSpec * The default implementation is equivalent to, for this {@code map}: * &lt;pre&gt; {@code * for (Map.Entry&lt;K, V&gt; entry : map.entrySet()) *     action.accept(entry.getKey(), entry.getValue()); * }&lt;/pre&gt; * * The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. * * @param action The action to be performed for each entry * @throws NullPointerException if the specified action is null * @throws ConcurrentModificationException if an entry is found to be * removed during iteration * @since 1.8 */default void forEach(BiConsumer&lt;? super K, ? super V&gt; action) {    Objects.requireNonNull(action);    for (Map.Entry&lt;K, V&gt; entry : entrySet()) {        K k;        V v;        try {            k = entry.getKey();            v = entry.getValue();        } catch(IllegalStateException ise) {            // this usually means the entry is no longer in the map.            throw new ConcurrentModificationException(ise);        }        action.accept(k, v);    }}</code></pre><p>功能：对Map中的每个键值对执行指定的操作。</p><h2 id="get-Object-"><a id="getobject">get(Object)</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * &lt;p&gt;More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}.  (There can be at most one such mapping.) * * &lt;p&gt;If this map permits null values, then a return value of * {@code null} does not &lt;i&gt;necessarily&lt;/i&gt; indicate that the map * contains no mapping for the key; it's also possible that the map * explicitly maps the key to {@code null}.  The {@link #containsKey * containsKey} operation may be used to distinguish these two cases. * * @param key the key whose associated value is to be returned * @return the value to which the specified key is mapped, or *         {@code null} if this map contains no mapping for the key * @throws ClassCastException if the key is of an inappropriate type for *         this map * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified key is null and this map *         does not permit null keys * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) */V get(Object key);</code></pre><p>功能：获取指定键对应的值，如果键不存在，则返回null。</p><h2 id="getOrDefault-Object--V-"><a id="getordefaultobject">getOrDefault(Object, V)</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns the value to which the specified key is mapped, or * {@code defaultValue} if this map contains no mapping for the key. * * @implSpec * The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. * * @param key the key whose associated value is to be returned * @param defaultValue the default mapping of the key * @return the value to which the specified key is mapped, or * {@code defaultValue} if this map contains no mapping for the key * @throws ClassCastException if the key is of an inappropriate type for * this map * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified key is null and this map * does not permit null keys * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @since 1.8 */default V getOrDefault(Object key, V defaultValue) {    V v;    return (((v = get(key)) != null) || containsKey(key))        ? v        : defaultValue;}</code></pre><p>功能：获取指定键对应的值，如果键不存在，则返回指定的默认值。</p><h2 id="hashCode--"><a id="hashcode">hashCode()</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns the hash code value for this map.  The hash code of a map is * defined to be the sum of the hash codes of each entry in the map's * &lt;tt&gt;entrySet()&lt;/tt&gt; view.  This ensures that &lt;tt&gt;m1.equals(m2)&lt;/tt&gt; * implies that &lt;tt&gt;m1.hashCode()==m2.hashCode()&lt;/tt&gt; for any two maps * &lt;tt&gt;m1&lt;/tt&gt; and &lt;tt&gt;m2&lt;/tt&gt;, as required by the general contract of * {@link Object#hashCode}. * * @return the hash code value for this map * @see Map.Entry#hashCode() * @see Object#equals(Object) * @see #equals(Object) */int hashCode();</code></pre><p>功能：返回 <code>Map</code> 对象的哈希码。</p><h2 id="isEmpty--"><a id="isempty">isEmpty()</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns &lt;tt&gt;true&lt;/tt&gt; if this map contains no key-value mappings. * * @return &lt;tt&gt;true&lt;/tt&gt; if this map contains no key-value mappings */boolean isEmpty();</code></pre><p>功能：判断Map是否为空</p><h2 id="keySet--"><a id="keyset">keySet()</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns a {@link Set} view of the keys contained in this map. * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa.  If the map is modified * while an iteration over the set is in progress (except through * the iterator's own &lt;tt&gt;remove&lt;/tt&gt; operation), the results of * the iteration are undefined.  The set supports element removal, * which removes the corresponding mapping from the map, via the * &lt;tt&gt;Iterator.remove&lt;/tt&gt;, &lt;tt&gt;Set.remove&lt;/tt&gt;, * &lt;tt&gt;removeAll&lt;/tt&gt;, &lt;tt&gt;retainAll&lt;/tt&gt;, and &lt;tt&gt;clear&lt;/tt&gt; * operations.  It does not support the &lt;tt&gt;add&lt;/tt&gt; or &lt;tt&gt;addAll&lt;/tt&gt; * operations. * * @return a set view of the keys contained in this map */Set&lt;K&gt; keySet();</code></pre><p>功能：返回 <code>Map</code> 中所有键的 <code>Set</code> 集合</p><h2 id="merge-K-V-BiFunction-V-V-V--"><a id="mergekvbifunctionvvv">merge(K,V,BiFunction&lt;V,V,V&gt;)</a></h2><p>源码：</p><pre><code class="language-java">/** * If the specified key is not already associated with a value or is * associated with null, associates it with the given non-null value. * Otherwise, replaces the associated value with the results of the given * remapping function, or removes if the result is {@code null}. This * method may be of use when combining multiple mapped values for a key. * For example, to either create or append a {@code String msg} to a * value mapping: * * &lt;pre&gt; {@code * map.merge(key, msg, String::concat) * }&lt;/pre&gt; * * &lt;p&gt;If the function returns {@code null} the mapping is removed.  If the * function itself throws an (unchecked) exception, the exception is * rethrown, and the current mapping is left unchanged. * * @implSpec * The default implementation is equivalent to performing the following * steps for this {@code map}, then returning the current value or * {@code null} if absent: * * &lt;pre&gt; {@code * V oldValue = map.get(key); * V newValue = (oldValue == null) ? value : *              remappingFunction.apply(oldValue, value); * if (newValue == null) *     map.remove(key); * else *     map.put(key, newValue); * }&lt;/pre&gt; * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. In particular, all implementations of * subinterface {@link java.util.concurrent.ConcurrentMap} must document * whether the function is applied once atomically only if the value is not * present. * * @param key key with which the resulting value is to be associated * @param value the non-null value to be merged with the existing value *        associated with the key or, if no existing value or a null value *        is associated with the key, to be associated with the key * @param remappingFunction the function to recompute a value if present * @return the new value associated with the specified key, or null if no *         value is associated with the key * @throws UnsupportedOperationException if the {@code put} operation *         is not supported by this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws ClassCastException if the class of the specified key or value *         prevents it from being stored in this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified key is null and this map *         does not support null keys or the value or remappingFunction is *         null * @since 1.8 */default V merge(K key, V value,        BiFunction&lt;? super V, ? super V, ? extends V&gt; remappingFunction) {    Objects.requireNonNull(remappingFunction);    Objects.requireNonNull(value);    V oldValue = get(key);    V newValue = (oldValue == null) ? value :               remappingFunction.apply(oldValue, value);    if(newValue == null) {        remove(key);    } else {        put(key, newValue);    }    return newValue;}</code></pre><p>功能：将指定的键和值合并到Map中，根据提供的函数计算新值。</p><h2 id="put-K-V-"><a id="putkv">put(K,V)</a></h2><p>源码：</p><pre><code class="language-java">/** * Associates the specified value with the specified key in this map * (optional operation).  If the map previously contained a mapping for * the key, the old value is replaced by the specified value.  (A map * &lt;tt&gt;m&lt;/tt&gt; is said to contain a mapping for a key &lt;tt&gt;k&lt;/tt&gt; if and only * if {@link #containsKey(Object) m.containsKey(k)} would return * &lt;tt&gt;true&lt;/tt&gt;.) * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with &lt;tt&gt;key&lt;/tt&gt;, or *         &lt;tt&gt;null&lt;/tt&gt; if there was no mapping for &lt;tt&gt;key&lt;/tt&gt;. *         (A &lt;tt&gt;null&lt;/tt&gt; return can also indicate that the map *         previously associated &lt;tt&gt;null&lt;/tt&gt; with &lt;tt&gt;key&lt;/tt&gt;, *         if the implementation supports &lt;tt&gt;null&lt;/tt&gt; values.) * @throws UnsupportedOperationException if the &lt;tt&gt;put&lt;/tt&gt; operation *         is not supported by this map * @throws ClassCastException if the class of the specified key or value *         prevents it from being stored in this map * @throws NullPointerException if the specified key or value is null *         and this map does not permit null keys or values * @throws IllegalArgumentException if some property of the specified key *         or value prevents it from being stored in this map */V put(K key, V value);</code></pre><p>功能：将指定的键值对添加到Map中。</p><h2 id="putAll-Map-K-V--"><a id="putallmapkv">putAll(Map&lt;K,V&gt;)</a></h2><p>源码：</p><pre><code class="language-java">/** * Copies all of the mappings from the specified map to this map * (optional operation).  The effect of this call is equivalent to that * of calling {@link #put(Object,Object) put(k, v)} on this map once * for each mapping from key &lt;tt&gt;k&lt;/tt&gt; to value &lt;tt&gt;v&lt;/tt&gt; in the * specified map.  The behavior of this operation is undefined if the * specified map is modified while the operation is in progress. * * @param m mappings to be stored in this map * @throws UnsupportedOperationException if the &lt;tt&gt;putAll&lt;/tt&gt; operation *         is not supported by this map * @throws ClassCastException if the class of a key or value in the *         specified map prevents it from being stored in this map * @throws NullPointerException if the specified map is null, or if *         this map does not permit null keys or values, and the *         specified map contains null keys or values * @throws IllegalArgumentException if some property of a key or value in *         the specified map prevents it from being stored in this map */void putAll(Map&lt;? extends K, ? extends V&gt; m);</code></pre><p>功能：将指定Map中的所有键值对添加到当前Map中。</p><h2 id="putIfAbsent-K-V-"><a id="putifabsentkv">putIfAbsent(K,V)</a></h2><p>源码：</p><pre><code class="language-java">/** * If the specified key is not already associated with a value (or is mapped * to {@code null}) associates it with the given value and returns * {@code null}, else returns the current value. * * @implSpec * The default implementation is equivalent to, for this {@code * map}: * * &lt;pre&gt; {@code * V v = map.get(key); * if (v == null) *     v = map.put(key, value); * * return v; * }&lt;/pre&gt; * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or *         {@code null} if there was no mapping for the key. *         (A {@code null} return can also indicate that the map *         previously associated {@code null} with the key, *         if the implementation supports null values.) * @throws UnsupportedOperationException if the {@code put} operation *         is not supported by this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws ClassCastException if the key or value is of an inappropriate *         type for this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified key or value is null, *         and this map does not permit null keys or values *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws IllegalArgumentException if some property of the specified key *         or value prevents it from being stored in this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @since 1.8 */default V putIfAbsent(K key, V value) {    V v = get(key);    if (v == null) {        v = put(key, value);    }    return v;}</code></pre><p>功能：将指定的键值对添加到Map中，但仅当指定的键在Map中不存在时。</p><h2 id="remove-Object-"><a id="removeobject">remove(Object)</a></h2><p>源码：</p><pre><code class="language-java">/** * Removes the mapping for a key from this map if it is present * (optional operation).   More formally, if this map contains a mapping * from key &lt;tt&gt;k&lt;/tt&gt; to value &lt;tt&gt;v&lt;/tt&gt; such that * &lt;code&gt;(key==null ?  k==null : key.equals(k))&lt;/code&gt;, that mapping * is removed.  (The map can contain at most one such mapping.) * * &lt;p&gt;Returns the value to which this map previously associated the key, * or &lt;tt&gt;null&lt;/tt&gt; if the map contained no mapping for the key. * * &lt;p&gt;If this map permits null values, then a return value of * &lt;tt&gt;null&lt;/tt&gt; does not &lt;i&gt;necessarily&lt;/i&gt; indicate that the map * contained no mapping for the key; it's also possible that the map * explicitly mapped the key to &lt;tt&gt;null&lt;/tt&gt;. * * &lt;p&gt;The map will not contain a mapping for the specified key once the * call returns. * * @param key key whose mapping is to be removed from the map * @return the previous value associated with &lt;tt&gt;key&lt;/tt&gt;, or *         &lt;tt&gt;null&lt;/tt&gt; if there was no mapping for &lt;tt&gt;key&lt;/tt&gt;. * @throws UnsupportedOperationException if the &lt;tt&gt;remove&lt;/tt&gt; operation *         is not supported by this map * @throws ClassCastException if the key is of an inappropriate type for *         this map * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified key is null and this *         map does not permit null keys * (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) */V remove(Object key);</code></pre><p>功能：移除Map中指定键对应的值。</p><h2 id="remove-Object-Object-"><a id="removeobjectobject">remove(Object,Object)</a></h2><p>源码：</p><pre><code class="language-java">/** * Removes the entry for the specified key only if it is currently * mapped to the specified value. * * @implSpec * The default implementation is equivalent to, for this {@code map}: * * &lt;pre&gt; {@code * if (map.containsKey(key) &amp;&amp; Objects.equals(map.get(key), value)) { *     map.remove(key); *     return true; * } else *     return false; * }&lt;/pre&gt; * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. * * @param key key with which the specified value is associated * @param value value expected to be associated with the specified key * @return {@code true} if the value was removed * @throws UnsupportedOperationException if the {@code remove} operation *         is not supported by this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws ClassCastException if the key or value is of an inappropriate *         type for this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified key or value is null, *         and this map does not permit null keys or values *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @since 1.8 */default boolean remove(Object key, Object value) {    Object curValue = get(key);    if (!Objects.equals(curValue, value) ||        (curValue == null &amp;&amp; !containsKey(key))) {        return false;    }    remove(key);    return true;}</code></pre><p>功能：移除Map中指定键对应的值，仅当该键关联的值与指定值相等时才移除。</p><h2 id="replace-K-V-V-"><a id="replacekvv">replace(K,V,V)</a></h2><p>源码：</p><pre><code class="language-java">/** * Replaces the entry for the specified key only if currently * mapped to the specified value. * * @implSpec * The default implementation is equivalent to, for this {@code map}: * * &lt;pre&gt; {@code * if (map.containsKey(key) &amp;&amp; Objects.equals(map.get(key), value)) { *     map.put(key, newValue); *     return true; * } else *     return false; * }&lt;/pre&gt; * * The default implementation does not throw NullPointerException * for maps that do not support null values if oldValue is null unless * newValue is also null. * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. * * @param key key with which the specified value is associated * @param oldValue value expected to be associated with the specified key * @param newValue value to be associated with the specified key * @return {@code true} if the value was replaced * @throws UnsupportedOperationException if the {@code put} operation *         is not supported by this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws ClassCastException if the class of a specified key or value *         prevents it from being stored in this map * @throws NullPointerException if a specified key or newValue is null, *         and this map does not permit null keys or values * @throws NullPointerException if oldValue is null and this map does not *         permit null values *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws IllegalArgumentException if some property of a specified key *         or value prevents it from being stored in this map * @since 1.8 */default boolean replace(K key, V oldValue, V newValue) {    Object curValue = get(key);    if (!Objects.equals(curValue, oldValue) ||        (curValue == null &amp;&amp; !containsKey(key))) {        return false;    }    put(key, newValue);    return true;}</code></pre><p>功能：将Map中指定键对应的旧值替换为新值，仅当键对应的值与旧值相等时才替换。</p><h2 id="replace-K-V-"><a id="replacekv">replace(K,V)</a></h2><p>源码：</p><pre><code class="language-java">/** * Replaces the entry for the specified key only if it is * currently mapped to some value. * * @implSpec * The default implementation is equivalent to, for this {@code map}: * * &lt;pre&gt; {@code * if (map.containsKey(key)) { *     return map.put(key, value); * } else *     return null; * }&lt;/pre&gt; * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties.  * * @param key key with which the specified value is associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or *         {@code null} if there was no mapping for the key. *         (A {@code null} return can also indicate that the map *         previously associated {@code null} with the key, *         if the implementation supports null values.) * @throws UnsupportedOperationException if the {@code put} operation *         is not supported by this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws ClassCastException if the class of the specified key or value *         prevents it from being stored in this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if the specified key or value is null, *         and this map does not permit null keys or values * @throws IllegalArgumentException if some property of the specified key *         or value prevents it from being stored in this map * @since 1.8 */default V replace(K key, V value) {    V curValue;    if (((curValue = get(key)) != null) || containsKey(key)) {        curValue = put(key, value);    }    return curValue;}</code></pre><p>功能：将Map中指定键对应的值替换为新值，返回旧值。</p><h2 id="replaceAll-BiFunction-K-V-V--"><a id="replaceallbifunctionkvv">replaceAll(BiFunction&lt;K,V,V&gt;)</a></h2><p>源码：</p><pre><code class="language-java">/** * Replaces each entry's value with the result of invoking the given * function on that entry until all entries have been processed or the * function throws an exception.  Exceptions thrown by the function are * relayed to the caller. * * @implSpec * &lt;p&gt;The default implementation is equivalent to, for this {@code map}: * &lt;pre&gt; {@code * for (Map.Entry&lt;K, V&gt; entry : map.entrySet()) *     entry.setValue(function.apply(entry.getKey(), entry.getValue())); * }&lt;/pre&gt; * * &lt;p&gt;The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. * * @param function the function to apply to each entry * @throws UnsupportedOperationException if the {@code set} operation * is not supported by this map's entry set iterator. * @throws ClassCastException if the class of a replacement value * prevents it from being stored in this map * @throws NullPointerException if the specified function is null, or the * specified replacement value is null, and this map does not permit null * values * @throws ClassCastException if a replacement value is of an inappropriate *         type for this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws NullPointerException if function or a replacement value is null, *         and this map does not permit null keys or values *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws IllegalArgumentException if some property of a replacement value *         prevents it from being stored in this map *         (&lt;a href="{@docRoot}/java/util/Collection.html#optional-restrictions"&gt;optional&lt;/a&gt;) * @throws ConcurrentModificationException if an entry is found to be * removed during iteration * @since 1.8 */default void replaceAll(BiFunction&lt;? super K, ? super V, ? extends V&gt; function) {    Objects.requireNonNull(function);    for (Map.Entry&lt;K, V&gt; entry : entrySet()) {        K k;        V v;        try {            k = entry.getKey();            v = entry.getValue();        } catch(IllegalStateException ise) {            // this usually means the entry is no longer in the map.            throw new ConcurrentModificationException(ise);        }        // ise thrown from function is not a cme.        v = function.apply(k, v);        try {            entry.setValue(v);        } catch(IllegalStateException ise) {            // this usually means the entry is no longer in the map.            throw new ConcurrentModificationException(ise);        }    }}</code></pre><p>功能：对Map中的每个键值对执行指定的替换操作。</p><h2 id="size--"><a id="size">size()</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns the number of key-value mappings in this map.  If the * map contains more than &lt;tt&gt;Integer.MAX_VALUE&lt;/tt&gt; elements, returns * &lt;tt&gt;Integer.MAX_VALUE&lt;/tt&gt;. * * @return the number of key-value mappings in this map */int size();</code></pre><p>功能：返回Map中键值对的数量。</p><h2 id="values--"><a id="values">values()</a></h2><p>源码：</p><pre><code class="language-java">/** * Returns a {@link Collection} view of the values contained in this map. * The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa.  If the map is * modified while an iteration over the collection is in progress * (except through the iterator's own &lt;tt&gt;remove&lt;/tt&gt; operation), * the results of the iteration are undefined.  The collection * supports element removal, which removes the corresponding * mapping from the map, via the &lt;tt&gt;Iterator.remove&lt;/tt&gt;, * &lt;tt&gt;Collection.remove&lt;/tt&gt;, &lt;tt&gt;removeAll&lt;/tt&gt;, * &lt;tt&gt;retainAll&lt;/tt&gt; and &lt;tt&gt;clear&lt;/tt&gt; operations.  It does not * support the &lt;tt&gt;add&lt;/tt&gt; or &lt;tt&gt;addAll&lt;/tt&gt; operations. * * @return a collection view of the values contained in this map */Collection&lt;V&gt; values();</code></pre><p>功能：返回包含Map中所有值的Collection集合。</p>]]>
                    </description>
                    <pubDate>Mon, 19 Feb 2024 13:33:30 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2024-01-27-跑章整理]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/2024-01-27--pao-zhang-zheng-li</link>
                    <description>
                            <![CDATA[<h1 id="总览">总览</h1><p>通过几天晚上的整理，规划出了今天的路线，大概上要去下面这些地方：辽宁美术馆，城市规划馆，万豪酒店，k11，广电博物馆，文化路万达，盛京龙城，盛京大家庭，大悦城乐高，全运路万达，在跑章的过程中，临时加入了大悦城霸王别姬</p><h1 id="计划">计划</h1><p>通过整理这些地方的地点和营业时间，初步按照下面的顺序依次跑章</p><h2 id="辽宁美术馆">辽宁美术馆</h2><ul><li>营业时间：9:00~17:00，16：00后不让进</li><li>路线：2号线 “市图书馆”，B口</li><li>盖章数：5枚</li></ul><h2 id="城市规划馆">城市规划馆</h2><ul><li>时间：9:00~17:00，16：00后不让进</li><li>路线：2号线”沈阳市图书馆站“，C口（辽宁美术馆步行过来700米）</li><li>盖章数：7枚</li><li>注意事项：需要带身份证</li></ul><h2 id="沈阳皇朝万豪酒店">沈阳皇朝万豪酒店</h2><ul><li>时间：2024.1.19～1.28，10：00～18：00</li><li>路线：2号线，五里河站，B1口（辽宁美术馆步行900米，城市规划馆步行900米）</li><li>盖章数：17枚</li><li>注意事项：分布在1楼和3楼的各个摊位，需要挨个问</li></ul><h2 id="k11">k11</h2><ul><li>路线：2号线，五里河，B1口（万豪酒店步行800米）</li><li>盖章数：共6枚，中信书店（2枚，3楼），歌德书店（2枚，2楼），西西弗书店（2枚，1楼）</li></ul><h2 id="广电博物馆">广电博物馆</h2><ul><li>时间：2024.1.27 10:00~15:30</li><li>路线：2号线，工业展览馆，C口（沈阳美术馆步行936米）</li></ul><h2 id="文化路万达">文化路万达</h2><ul><li>时间：10:00~22:00</li><li>路线： 2号线，工业展览馆，C口</li></ul><h2 id="盛京龙城">盛京龙城</h2><ul><li>盖章数：14枚</li><li>注意事项：1楼盛京阿哥</li></ul><h2 id="盛京大家庭">盛京大家庭</h2><ul><li>时间：2024.1.26 ~ 2024.1.30</li><li>注意事项：负一层大脸鸭记</li></ul><h2 id="大悦城乐高">大悦城乐高</h2><ul><li>盖章数：3枚</li><li>注意事项： 中街乐高，大悦城C馆一进门那个，泡泡玛特对面</li></ul>]]>
                    </description>
                    <pubDate>Mon, 05 Feb 2024 14:40:18 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[win11新电脑环境安装]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/win11-xin-dian-nao-huan-jing-an-zhuang</link>
                    <description>
                            <![CDATA[<p>新的mini主机到了，为了之后的开发方便，需要先安装各种软件，这里记录下需要安装的软件，我这边是以Java为主</p><h1 id="Java">Java</h1><p>我这边Java下载安装的是17版本的，下载地址：<a href="https://www.oracle.com/java/technologies/downloads/#jdk17-windows">Java Downloads | Oracle</a>，下面是下载页面，根据自己电脑的情况安装不同的版本</p><p><img src="https://img.huangge1199.cn/blog/win11xin-dian-nao-huan-jing-an-zhuang/2024-02-04-09-41-42-image.png" alt=""></p><h1 id="maven">maven</h1><p>我使用的是3.6.3，下载地址：<a href="https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/">maven</a></p><p><img src="https://img.huangge1199.cn/blog/win11xin-dian-nao-huan-jing-an-zhuang/2024-02-04-10-27-27-image.png" alt=""></p><h1 id="nvm">nvm</h1><p>安装包在GitHub中下载的，安装说明也挺详细，地址：<a href="https://github.com/coreybutler/nvm-windows">nvm-windows</a></p><h1 id="git">git</h1><p>官网下载地址：<a href="https://git-scm.com/download">git</a></p><p><img src="https://img.huangge1199.cn/blog/win11xin-dian-nao-huan-jing-an-zhuang/2024-02-04-13-54-17-image.png" alt=""></p><h1 id="nodejs">nodejs</h1><p>通过nvm安装LTS版本</p>]]>
                    </description>
                    <pubDate>Sun, 04 Feb 2024 14:34:34 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[1686. 石子游戏 VI（2024-02-02）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/1686-shi-zi-you-xi-vi2024-02-02</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br>题目：<a href="https://leetcode.cn/problems/stone-game-vi/">1686. 石子游戏 VI</a></p><p><img src="https://img.huangge1199.cn/halo/2024-02-02.png" alt="2024-02-02.png"></p><p>日期：2024-02-02<br>用时：15 m 0 s<br>时间：103ms<br>内存：57.95MB<br>代码：</p><pre><code class="language-java">class Solution {    public int stoneGameVI(int[] aliceValues, int[] bobValues) {        int cnt = aliceValues.length;        int[][] arrs = new int[cnt][2];        for (int i = 0; i &lt; cnt; i++) {            arrs[i] = new int[]{aliceValues[i],bobValues[i]};        }        Arrays.sort(arrs,(a,b)-&gt;(b[0]+ b[1])-(a[0]+ a[1]));        int sub = 0;        for (int i = 0; i &lt; cnt; i++) {            sub+=i%2==0?arrs[i][0]:-arrs[i][1];        }        return sub == 0? 0 :sub / Math.abs(sub);    }}</code></pre>]]>
                    </description>
                    <pubDate>Fri, 02 Feb 2024 15:55:00 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[vue--excel上传]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p517e8911-18f6-4f21-bc73-2847d22cd631</link>
                    <description>
                            <![CDATA[<p>在vue中实现excel上传并显示数据<br />效果如下：<br /><mew-video loop width="80%" src="https://img.huangge1199.cn/halo/vue--excel%E4%B8%8A%E4%BC%A0.mp4"></mew-video><br />具体代码如下：</p><pre><code class="language-html">&lt;template&gt;  &lt;div class=&quot;app-container&quot;&gt;      &lt;input        ref=&quot;excel-upload-input&quot;        class=&quot;excel-upload-input&quot;        style=&quot;width: 300px;margin-left: 10px&quot;        type=&quot;file&quot;        accept=&quot;.xlsx, .xls&quot;        @change=&quot;handleClick&quot;      /&gt;      &lt;div class=&quot;drop&quot; @drop=&quot;handleDrop&quot; @dragover=&quot;handleDragover&quot; @dragenter=&quot;handleDragover&quot;&gt;        批量导入：拖拽excel文件或者        &lt;el-button :loading=&quot;loading&quot; style=&quot;margin-left:16px;&quot; size=&quot;mini&quot; type=&quot;primary&quot; @click=&quot;handleUpload&quot;&gt;          浏览        &lt;/el-button&gt;      &lt;/div&gt;    &lt;el-table :data=&quot;excelData.results&quot; border highlight-current-row style=&quot;width: 100%;margin-top:20px;&quot;&gt;      &lt;el-table-column v-for=&quot;item of excelData.header&quot; :key=&quot;item&quot; :prop=&quot;item&quot; :label=&quot;item&quot;/&gt;    &lt;/el-table&gt;  &lt;/div&gt;&lt;/template&gt;&lt;script&gt;import XLSX from 'xlsx'export default {  name: 'UploadExcel',  data() {    return {      loading: false,      excelData: {        header: null,        results: null      }    }  },  methods: {    handleUpload() {      this.$refs['excel-upload-input'].click()    },    handleClick(e) {      const files = e.target.files      const file = files[0]      const before = this.beforeUpload(file)      if (before) {        this.upload(file)      }    },    // 上传文件的条件验证    beforeUpload(file) {      if (!file) {        return false      }      const isLt1M = file.size / 1024 / 1024 &lt; 1      if (isLt1M) {        return true      }      this.$message({        message: '请上传1M以内的文件',        type: 'warning'      })      return false    },    // excel文件数据获取    upload(file) {      this.$refs['excel-upload-input'].value = null      return new Promise((resolve, reject) =&gt; {        const reader = new FileReader()        reader.onload = e =&gt; {          const data = e.target.result          const workbook = XLSX.read(data, { type: 'array' })          // 获取第一个sheet页名字          const firstSheetName = workbook.SheetNames[0]          // 获取指定名字的sheet页内容          const worksheet = workbook.Sheets[firstSheetName]          // 获取标头          const header = this.getHeaderRow(worksheet)          // 获取除标头外的数据          const results = XLSX.utils.sheet_to_json(worksheet)          // 标头和数据处理          this.generateData({ header, results })          resolve()        }        reader.readAsArrayBuffer(file)      })    },    // 获取标头    getHeaderRow(sheet) {      const headers = []      const range = XLSX.utils.decode_range(sheet['!ref'])      let C      const R = range.s.r      /* start in the first row */      for (C = range.s.c; C &lt;= range.e.c; ++C) { /* walk every column in the range */        const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]        /* find the cell in the first row */        let hdr = 'UNKNOWN ' + C // &lt;-- replace with your desired default        if (cell &amp;&amp; cell.t) hdr = XLSX.utils.format_cell(cell)        headers.push(hdr)      }      return headers    },    handleDrop(e) {      e.stopPropagation()      e.preventDefault()      if (this.loading) return      const files = e.dataTransfer.files      if (files.length !== 1) {        this.$message.error('仅支持上传一个文件!')        return      }      const rawFile = files[0] // only use files[0]      if (!this.isExcel(rawFile)) {        this.$message.error('仅支持上传.xlsx, .xls, .csv格式的文件')        return false      }      this.upload(rawFile)      e.stopPropagation()      e.preventDefault()    },    handleDragover(e) {      e.stopPropagation()      e.preventDefault()      e.dataTransfer.dropEffect = 'copy'    },    // 标头和数据处理（可以在此方法中调用后端接口保存数据）    generateData({ header, results }) {      this.excelData.header = header      this.excelData.results = results    }  }}&lt;/script&gt;&lt;style scoped&gt;.excel-upload-input {  display: none;  z-index: -9999;}.drop {  border: 2px dashed #bbb;  width: 600px;  height: 160px;  line-height: 160px;  margin: 0 auto;  font-size: 24px;  border-radius: 5px;  text-align: center;  color: #bbb;  position: relative;}&lt;/style&gt;</code></pre>]]>
                    </description>
                    <pubDate>Thu, 21 Dec 2023 11:43:24 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[162. 寻找峰值（2023-12-18）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p77b189fe-76ed-4be8-801c-0e690e7b0bc1</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/find-peak-element/description/">162. 寻找峰值</a></p><p><img src="https://img.huangge1199.cn/halo/2023-12-18.png" alt="2023-12-18.png" /><br />日期：2023-12-18<br />用时：10 m 9 s<br />时间：0 ms<br />内存：40.54 MB<br />代码：</p><pre><code class="language-java">class Solution {    public int findPeakElement(int[] nums) {        if(nums.length==1){            return 0;        }        if(nums.length==2){            return nums[0]&gt;nums[1]?0:1;        }        if(nums[0]&gt;nums[1]){            return 0;        }        if(nums[nums.length-1]&gt;nums[nums.length-2]){            return nums.length-1;        }        for(int i=1;i&lt;nums.length-1;i++){            if(nums[i]&gt;nums[i-1]&amp;&amp;nums[i]&gt;nums[i+1]){                return i;            }        }        return 0;    }}</code></pre>]]>
                    </description>
                    <pubDate>Mon, 18 Dec 2023 11:05:33 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2415. 反转二叉树的奇数层（2023-12-15）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/pff3c4b7a-4913-45d2-80cc-b2421f043ccb</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/reverse-odd-levels-of-binary-tree/description/">2415. 反转二叉树的奇数层</a><br /><img src="https://img.huangge1199.cn/halo/2023-12-15.png" alt="2023-12-15.png" /><br />日期：2023-12-15<br />用时：6 m 51 s<br />时间：0 ms<br />内存：46.97 MB<br />代码：</p><pre><code class="language-java">/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode() {} *     TreeNode(int val) { this.val = val; } *     TreeNode(int val, TreeNode left, TreeNode right) { *         this.val = val; *         this.left = left; *         this.right = right; *     } * } */class Solution {    public TreeNode reverseOddLevels(TreeNode root) {        dfs(root.left, root.right, 1);        return root;    }    void dfs(TreeNode left,TreeNode right,int odd){        if(left==null){            return;        }        if(odd==1){            int temp = left.val;            left.val = right.val;            right.val = temp;        }        dfs(left.left, right.right, 1-odd);        dfs(left.right, right.left, 1-odd);    }}</code></pre>]]>
                    </description>
                    <pubDate>Fri, 15 Dec 2023 09:38:59 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2132. 用邮票贴满网格图（2023-12-14）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p6550efef-5573-44e3-b326-2e2d31689e60</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/stamping-the-grid/description/">2132. 用邮票贴满网格图</a><br /><img src="https://img.huangge1199.cn/halo/2023-12-14.png" alt="2023-12-14.png" /><br />日期：2023-12-14<br />用时：38 m 32 s<br />思路：使用前缀和＋差分，只是往常是一维，现在变二维了，原理差不多<br />时间：22ms<br />内存：98.24MB<br />代码：</p><pre><code class="language-java">class Solution {    public boolean possibleToStamp(int[][] grid, int stampHeight, int stampWidth) {        int xl = grid.length;        int yl = grid[0].length;        // 前缀和        int[][] sum = new int[xl+1][yl+1];        for(int i=1;i&lt;=xl;i++){            for(int j=1;j&lt;=yl;j++){                sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+grid[i-1][j-1];            }        }        // 差分        int[][] cnt = new int[xl+2][yl+2];        for(int xStart=stampHeight;xStart&lt;=xl;xStart++){            for(int yStart=stampWidth;yStart&lt;=yl;yStart++){                int xEnd = xStart-stampHeight+1;                int yEnd = yStart-stampWidth+1;                if(sum[xStart][yStart]+sum[xEnd-1][yEnd-1]-sum[xStart][yEnd-1]-sum[xEnd-1][yStart]==0){                    cnt[xEnd][yEnd]++;                    cnt[xStart+1][yStart+1]++;                    cnt[xEnd][yStart+1]--;                    cnt[xStart+1][yEnd]--;                }            }        }        // 判断单元格是否能放邮戳        for(int i=1;i&lt;=xl;i++){            for(int j=1;j&lt;=yl;j++){                cnt[i][j] += cnt[i][j-1]+cnt[i-1][j]-cnt[i-1][j-1];                if(grid[i-1][j-1]==0&amp;&amp;cnt[i][j]==0){                    return false;                }            }        }        return true;    }}</code></pre>]]>
                    </description>
                    <pubDate>Thu, 14 Dec 2023 09:33:32 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2697. 字典序最小回文串（2023-12-13）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/pa0401e89-5259-4fd7-8312-3325694fb0e6</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/lexicographically-smallest-palindrome/description/">2697. 字典序最小回文串</a><br /><img src="https://img.huangge1199.cn/halo/2023-12-13.png" alt="2023-12-13.png" /><br />日期：2023-12-13<br />用时：4 m 53 s<br />时间：7ms<br />内存：43.61MB<br />代码：</p><pre><code class="language-java">class Solution {    public String makeSmallestPalindrome(String s) {        char[] chs = s.toCharArray();        int size = s.length();        for(int i=0;i&lt;size/2;i++){            if(chs[i]&gt;chs[size-1-i]){                chs[i] = chs[size-1-i];            }else{                chs[size-1-i] = chs[i];            }        }        return new String(chs);    }}</code></pre>]]>
                    </description>
                    <pubDate>Wed, 13 Dec 2023 08:58:15 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2454. 下一个更大元素 IV（2023-12-12）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p85597755-36db-4a40-8dfc-1a9317659a4e</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/next-greater-element-iv/description/">2454. 下一个更大元素 IV</a><br /><img src="https://img.huangge1199.cn/halo/2023-12-12.png" alt="2023-12-12.png" /><br />日期：2023-12-12<br />用时：35 m 09 s<br />时间：614ms<br />内存：57.18MB<br />代码：</p><pre><code class="language-java">class Solution {    public int[] secondGreaterElement(int[] nums) {        int[] res = new int[nums.length];        Arrays.fill(res, -1);        List&lt;Integer&gt; list1 = new ArrayList&lt;&gt;();        List&lt;Integer&gt; list2 = new ArrayList&lt;&gt;();        for (int i = 0; i &lt; nums.length; i++) {            while (!list2.isEmpty() &amp;&amp; nums[list2.get(list2.size() - 1)] &lt; nums[i]) {                res[list2.get(list2.size() - 1)] = nums[i];                list2.remove(list2.size() - 1);            }            int j = list1.size();            for(;j&gt;0;j--){                if(nums[list1.get(j - 1)] &gt;= nums[i]){                    break;                }            }            while (j&lt;list1.size()) {                list2.add(list1.get(j));                list1.remove(j);            }            list1.add(i);        }        return res;    }}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 12 Dec 2023 13:58:58 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[沈阳四家万达（2023-12-09、2023-12-10）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p3ff2becf-4a94-411b-b580-eb81ce8d4e1d</link>
                    <description>
                            <![CDATA[<p>全运路万达8枚<br />铁西万达4枚<br />北一路万达16枚<br />太原街万达8枚</p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_17_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_17_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_18_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_18_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_14_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_14_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_13_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_13_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_16_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_16_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_15_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_15_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_11_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_11_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_12_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_12_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_9_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_9_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_10_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_10_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_3_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_3_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_2_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_2_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_4_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_4_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_5_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_5_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_1_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_1_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_7_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_7_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_8_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_8_夏夜晚风_来自小红书网页版.jpg" /></p><p><img src="https://img.huangge1199.cn/halo/%E6%B2%88%E9%98%B3%E5%9B%9B%E5%AE%B6%E4%B8%87%E8%BE%BE%E7%9B%96%E7%AB%A0%E6%89%93%E5%8D%A1_6_%E5%A4%8F%E5%A4%9C%E6%99%9A%E9%A3%8E_%E6%9D%A5%E8%87%AA%E5%B0%8F%E7%BA%A2%E4%B9%A6%E7%BD%91%E9%A1%B5%E7%89%88.jpg" alt="沈阳四家万达盖章打卡_6_夏夜晚风_来自小红书网页版.jpg" /></p><p>(摘自小红书<a href="https://www.xiaohongshu.com/explore/65758b310000000006020803?m_source=mengfanwetab">https://www.xiaohongshu.com/explore/65758b310000000006020803?m_source=mengfanwetab</a>)</p>]]>
                    </description>
                    <pubDate>Mon, 11 Dec 2023 13:47:41 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2008. 出租车的最大盈利（2023-12-08）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/pf8bfc9b6-6a51-4b42-af13-a45a4abc1688</link>
                    <description>
                            <![CDATA[<p>力扣每日一题</p><h1 id="题目2008-出租车的最大盈利">题目：<a href="https://leetcode.cn/problems/maximum-earnings-from-taxi/description/">2008. 出租车的最大盈利</a></h1><p><img src="https://img.huangge1199.cn/halo/2023-12-08.png" alt="2023-12-08.png" /></p><h1 id="简短说明">简短说明</h1><p>今天的解题有点曲折，完全是一步一步优化来的，看上面的截图，最开始的超时，超时后我加了记忆化搜索，虽然通过了，但是执行时间不太理想，接下来我稍微优化了下，但是执行时间基本没动过，接下来，又尝试着去掉递归，这次效果很显著，执行时间直接从2000多毫秒降低到了18毫秒</p><h1 id="过程">过程</h1><p>下面我分别把这四次的代码都展示出来，记录下每次的优化，代码展示顺序是按照上面的截图从下往上的</p><h2 id="超出时间限制">超出时间限制</h2><pre><code class="language-java">class Solution {    public long maxTaxiEarnings(int n, int[][] rides) {      List&lt;int[]&gt;[] prices = new ArrayList[n+1];      for(int[] ride:rides){        if(prices[ride[1]]==null){          prices[ride[1]] = new ArrayList&lt;&gt;();        }        prices[ride[1]].add(new int[]{ride[0],ride[1]-ride[0]+ride[2]});      }      return dfs(n,prices);    }    long dfs(int index,List&lt;int[]&gt;[] prices){      if(index==1){        return 0;      }      long res = dfs(index-1,prices);      if(prices[index]!=null){        for(int[] price:prices[index]){          res = Math.max(res,dfs(price[0],prices)+price[1]);        }      }      return res;    }}</code></pre><h2 id="2219ms--848mb">2219ms + 84.8MB</h2><pre><code class="language-java">class Solution {    public long maxTaxiEarnings(int n, int[][] rides) {      List&lt;int[]&gt;[] prices = new ArrayList[n+1];      for(int[] ride:rides){        if(prices[ride[1]]==null){          prices[ride[1]] = new ArrayList&lt;&gt;();        }        prices[ride[1]].add(new int[]{ride[0],ride[1]-ride[0]+ride[2]});      }      max = new long[n+1];      return dfs(n,prices);    }     long[] max;    long dfs(int index,List&lt;int[]&gt;[] prices){      if(index==1){        return 0;      }      long res = dfs(index-1,prices);      if(prices[index]!=null){        for(int[] price:prices[index]){          if(max[price[0]]&gt;0){            res = Math.max(res,max[price[0]]+price[1]);          }else{            res = Math.max(res,dfs(price[0],prices)+price[1]);          }        }      }      max[index] = res;      return res;    }}</code></pre><h2 id="2306ms--842mb">2306ms + 84.2MB</h2><pre><code class="language-java">class Solution {    public long maxTaxiEarnings(int n, int[][] rides) {      List&lt;int[]&gt;[] prices = new ArrayList[n+1];      for(int[] ride:rides){        if(prices[ride[1]]==null){          prices[ride[1]] = new ArrayList&lt;&gt;();        }        prices[ride[1]].add(new int[]{ride[0],ride[1]-ride[0]+ride[2]});      }      max = new long[n+1];      return dfs(n,prices);    }     long[] max;    long dfs(int index,List&lt;int[]&gt;[] prices){      if(index==1){        return 0;      }      if(max[index]&gt;0){        return max[index];      }      long res = dfs(index-1,prices);      if(prices[index]!=null){        for(int[] price:prices[index]){          res = Math.max(res,dfs(price[0],prices)+price[1]);        }      }      max[index] = res;      return res;    }}</code></pre><h2 id="18ms--673mb">18ms + 67.3MB</h2><pre><code class="language-java">class Solution {    public long maxTaxiEarnings(int n, int[][] rides) {      List&lt;int[]&gt;[] prices = new ArrayList[n+1];      for(int[] ride:rides){        if(prices[ride[1]]==null){          prices[ride[1]] = new ArrayList&lt;&gt;();        }        prices[ride[1]].add(new int[]{ride[0],ride[1]-ride[0]+ride[2]});      }      long[] max = new long[n+1];      for(int i=2;i&lt;=n;i++){        max[i] = max[i-1];        if(prices[i]!=null){          for(int[] price:prices[i]){            max[i] = Math.max(max[i],max[price[0]]+price[1]);          }        }      }      return max[n];    }}</code></pre>]]>
                    </description>
                    <pubDate>Fri, 08 Dec 2023 13:50:54 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[1466. 重新规划路线（2023-12-07）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p7c22d9d4-56e7-4b5c-9d02-f3c3ee77ac28</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/reorder-routes-to-make-all-paths-lead-to-the-city-zero/description/">1466. 重新规划路线</a><br /><img src="https://img.huangge1199.cn/halo/2023-12-07.png" alt="2023-12-07.png" /><br />日期：2023-12-07<br />用时：45 m 36 s<br />时间：37ms<br />内存：69.64MB<br />代码：</p><pre><code class="language-java">class Solution {    public int minReorder(int n, int[][] connections) {        list = new List[n];        Arrays.setAll(list, k -&gt; new ArrayList&lt;&gt;());        for (int[] connection : connections) {            int start = connection[0];            int end = connection[1];            list[start].add(new int[] {end, 1});            list[end].add(new int[] {start, 0});        }        return dfs(0, -1);    }        List&lt;int[]&gt;[] list;    private int dfs(int index, int target) {        int ans = 0;        for (int[] num : list[index]) {            if (num[0] != target) {                ans += num[1] + dfs(num[0], index);            }        }        return ans;    }}</code></pre>]]>
                    </description>
                    <pubDate>Thu, 07 Dec 2023 15:39:14 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2646. 最小化旅行的价格总和（2023-12-06）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p9e12f7d5-64c5-48d8-9112-59009e79517d</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/minimize-the-total-price-of-the-trips/description/">2646. 最小化旅行的价格总和</a><br /><img src="https://img.huangge1199.cn/halo/2023-12-06.png" alt="2023-12-06.png" /><br />日期：2023-12-06<br />用时：30 m 14 s<br />时间：8ms<br />内存：42.98MB<br />思路：每条路上通过的城市数量实际就是图中每个节点的子节点数量，先统计旅行中每个节点路过的次数（dfs方法），再计算减半后的价格之和的最小值（dp方法），最后比较下减半和未减半的价格。dp方法中，对于相邻的父子节点有两种情况：</p><ul><li>如果父节点价格不变，那么子节点的价格取减半和不变两种情况的最小值</li><li>如果父节点价格减半，那么子节点的价格只能不变</li></ul><p>代码：</p><pre><code class="language-java">class Solution {    public int minimumTotalPrice(int n, int[][] edges, int[] price, int[][] trips) {      list = new ArrayList[n];      for(int i=0;i&lt;n;i++){        list[i] = new ArrayList&lt;&gt;();      }      for(int[] edge:edges){        list[edge[0]].add(edge[1]);        list[edge[1]].add(edge[0]);      }      cnt = new int[n];      for(int[] trip:trips){        end = trip[1];        dfs(trip[0],-1);      }      int[] res = dp(0,-1,price);      return Math.min(res[0],res[1]);    }    List&lt;Integer&gt;[] list;    int end;    int[] cnt;    boolean dfs(int x, int fa) {        if (x == end) {            cnt[x]++;            return true;        }        for (int y : list[x]) {            if (y != fa &amp;&amp; dfs(y, x)) {                cnt[x]++;                return true;            }        }        return false;    }    int[] dp(int index,int target,int[] price){      int prices = price[index]*cnt[index];      int halfPrices = prices/2;      for(int num:list[index]){        if(num!=target){          int[] res = dp(num,index,price);          prices += Math.min(res[0],res[1]);          halfPrices += res[0];        }      }      return new int[]{prices,halfPrices};    }}</code></pre>]]>
                    </description>
                    <pubDate>Wed, 06 Dec 2023 21:48:12 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2477. 到达首都的最少油耗（2023-12-05）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p75c16288-b5f0-4d32-877a-ca9eb29ebd76</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/minimum-fuel-cost-to-report-to-the-capital/description/">2477. 到达首都的最少油耗</a><br /><img src="https://img.huangge1199.cn/halo/2023-12-05.png" alt="2023-12-05.png" /><br />日期：2023-12-05<br />用时：34 m 15 s<br />时间：37ms<br />内存：84.8MB<br />思路：分别计算每条路上通过的城市数量（数量/座位数，向上取整），然后求和，这里每条路上通过的城市数量实际就是图中每个节点的子节点数量。<br />代码：每条路上通过的城市数量实际就是图中每个节点的子节点数量。</p><pre><code class="language-java">class Solution {    public long minimumFuelCost(int[][] roads, int seats) {        int size = roads.length+1;        List&lt;Integer&gt;[] list = new ArrayList[size];        for(int i=0;i&lt;size;i++){            list[i] = new ArrayList&lt;&gt;();        }        for(int[] road:roads){            int num1 = road[0];            int num2 = road[1];            list[num1].add(num2);            list[num2].add(num1);        };        dfs(0,-1,list,seats);        return sum;    }    long sum = 0;    private int dfs(int start,int end,List&lt;Integer&gt;[] list,int seats){        int cnt =1;        for(int num: list[start]){            if(num!=end){                cnt+=dfs(num,start,list,seats);            }        }        if(start&gt;0){            sum+=(cnt-1)/seats+1;        }        return cnt;    }}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 05 Dec 2023 09:56:50 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[1038. 从二叉搜索树到更大和树（2023-12-04）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p0e037f8b-087a-45d2-b545-fdd06cc8c853</link>
                    <description>
                            <![CDATA[<pre><code>力扣每日一题题目：[1038. 从二叉搜索树到更大和树](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/description/)![2023-12-04.png](https://img.huangge1199.cn/halo/2023-12-04.png)日期：2023-12-04用时：12 m 23 s时间：0ms内存：39.39MB代码：```java/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode() {} *     TreeNode(int val) { this.val = val; } *     TreeNode(int val, TreeNode left, TreeNode right) { *         this.val = val; *         this.left = left; *         this.right = right; *     } * } */class Solution {    public TreeNode bstToGst(TreeNode root) {        dfs(root);        return root;    }    int sum = 0;    private void dfs(TreeNode node) {        if (node == null) {            return;        }        dfs(node.right);        sum += node.val;        node.val = sum;        dfs(node.left);    }}```</code></pre>]]>
                    </description>
                    <pubDate>Mon, 04 Dec 2023 10:25:19 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2661. 找出叠涂元素（2023-12-01）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p9063fd05-b73c-432d-9cfc-03cb28dc564b</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/first-completely-painted-row-or-column/description/">2661. 找出叠涂元素</a></p><p><img src="https://img.huangge1199.cn/halo/2023-12-01.png" alt="2023-12-01.png" /><br />日期：2023-12-01<br />用时：7 m 4 s<br />时间：26ms<br />内存：67.45MB<br />代码：</p><pre><code class="language-java">class Solution {    public int firstCompleteIndex(int[] arr, int[][] mat) {        Map&lt;Integer,int[]&gt; map = new HashMap&lt;&gt;();        for(int i=0;i&lt;mat.length;i++){            for(int j=0;j&lt;mat[0].length;j++){                map.put(mat[i][j],new int[]{i,j});            }        }        int[] xc = new int[mat.length];        int[] yc = new int[mat[0].length];        for(int i=0;i&lt;arr.length;i++){            int[] tmp = map.get(arr[i]);            xc[tmp[0]]++;            yc[tmp[1]]++;            if(xc[tmp[0]]==mat[0].length||yc[tmp[1]]==mat.length){                return i;            }        }        return 0;    }}</code></pre>]]>
                    </description>
                    <pubDate>Fri, 01 Dec 2023 09:41:16 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[1657. 确定两个字符串是否接近（2023-11-30）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p8fb111f3-b3e8-446a-adda-ba36513f34ba</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/determine-if-two-strings-are-close/description/">1657. 确定两个字符串是否接近</a></p><p><img src="https://img.huangge1199.cn/halo/2023-11-30.png" alt="2023-11-30.png" /><br />日期：2023-11-30<br />用时：21 m 07 s<br />时间：11ms<br />内存：43.70MB<br />代码：</p><pre><code class="language-java">class Solution {    public boolean closeStrings(String word1, String word2) {        if(word1.length()!=word2.length()){            return false;        }        int[] arr1 = new int[26];        int[] arr2 = new int[26];        int mask1=0;        int mask2=0;        for(int i=0;i&lt;word1.length();i++){            arr1[word1.charAt(i)-'a']++;            arr2[word2.charAt(i)-'a']++;            mask1 |= 1&lt;&lt;(word1.charAt(i)-'a');            mask2 |= 1&lt;&lt;(word2.charAt(i)-'a');        }        Arrays.sort(arr1);        Arrays.sort(arr2);        return Arrays.equals(arr1,arr2)&amp;&amp;mask1==mask2;    }}</code></pre>]]>
                    </description>
                    <pubDate>Thu, 30 Nov 2023 10:25:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[2336. 无限集中的最小数字（2023.11.29）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/p0d16179b-eeff-4ab5-8e13-9a73af8aa990</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/smallest-number-in-infinite-set/description/">2336. 无限集中的最小数字</a><br /><img src="https://img.huangge1199.cn/halo/2023-11-29.png" alt="2023-11-29.png" /><br />日期：2023-11-29<br />用时：3 m 50 s<br />时间：71ms<br />内存：43.68MB<br />代码：</p><pre><code class="language-java">class SmallestInfiniteSet {    List&lt;Integer&gt; list;    public SmallestInfiniteSet() {        list = new ArrayList&lt;&gt;();        for(int i=1;i&lt;1001;i++){            list.add(i);        }        Collections.sort(list);    }        public int popSmallest() {        int num = list.get(0);        list.remove(0);        return num;    }        public void addBack(int num) {        if(!list.contains(num)){            list.add(num);            Collections.sort(list);        }    }}/** * Your SmallestInfiniteSet object will be instantiated and called as such: * SmallestInfiniteSet obj = new SmallestInfiniteSet(); * int param_1 = obj.popSmallest(); * obj.addBack(num); */</code></pre>]]>
                    </description>
                    <pubDate>Wed, 29 Nov 2023 09:56:36 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣每日一题（2023.11.28）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/li-kou-mei-ri-yi-ti-20231128</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/design-front-middle-back-queue/description/" target="_blank">1670. 设计前中后队列</a><br />日期：2023-11-28<br />用时：8 m 23 s<br />时间：6ms<br />内存：43.55MB<br />代码：</p><pre><code class="language-java">class FrontMiddleBackQueue {    List&lt;Integer&gt; list;    public FrontMiddleBackQueue() {        list = new ArrayList&lt;&gt;();    }        public void pushFront(int val) {        list.add(0,val);    }        public void pushMiddle(int val) {        list.add(list.size()/2,val);    }        public void pushBack(int val) {        list.add(val);    }        public int popFront() {        if(list.size()==0){            return -1;        }        int res = list.get(0);        list.remove(0);        return res;    }        public int popMiddle() {        if(list.size()==0){            return -1;        }        int res = list.get((list.size()-1)/2);        list.remove((list.size()-1)/2);        return res;    }        public int popBack() {        if(list.size()==0){            return -1;        }        int res = list.get(list.size()-1);        list.remove(list.size()-1);        return res;    }}/** * Your FrontMiddleBackQueue object will be instantiated and called as such: * FrontMiddleBackQueue obj = new FrontMiddleBackQueue(); * obj.pushFront(val); * obj.pushMiddle(val); * obj.pushBack(val); * int param_4 = obj.popFront(); * int param_5 = obj.popMiddle(); * int param_6 = obj.popBack(); */</code></pre>]]>
                    </description>
                    <pubDate>Tue, 28 Nov 2023 16:01:55 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣每日一题（2023.11.27）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/li-kou-mei-ri-yi-ti-20231127</link>
                    <description>
                            <![CDATA[<p>力扣每日一题<br />题目：<a href="https://leetcode.cn/problems/sum-of-subarray-minimums/description/" target="_blank">907. 子数组的最小值之和</a><br />日期：2023-11-27<br />用时：14 m 14 s<br />时间：19ms<br />内存：47.42MB<br />代码：</p><pre><code class="language-java">class Solution {    public int sumSubarrayMins(int[] arr) {        int n=arr.length;        int res = 0;        int mod=1000000007;        Deque&lt;Integer&gt; deque=new ArrayDeque&lt;&gt;();        for (int i=0; i &lt;= n; i++) {            int cur = i&lt;n?arr[i] : 0;            while (!deque.isEmpty() &amp;&amp; arr[deque.peekLast()] &gt;= cur) {                int index = deque.pollLast();                int l=deque.isEmpty()?-1:deque.peekLast();                res += 1L*(index-l)*(i-index)%mod*arr[index]%mod;                res %= mod;            }            deque.addLast(i);        }        return res;    }}</code></pre>]]>
                    </description>
                    <pubDate>Mon, 27 Nov 2023 09:39:30 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[ Windows系统下设置程序开机自启（WinSW）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/windows-xi-tong-xia-she-zhi-cheng-xu-kai-ji-zi-qi-winsw</link>
                    <description>
                            <![CDATA[<h1 id="%E4%BB%8B%E7%BB%8D" tabindex="-1">介绍</h1><p>WinSW可以将Windows上的任何程序作为系统服务进行管理，已达到开机自启的效果。</p><h1 id="%E6%94%AF%E6%8C%81%E7%9A%84%E5%B9%B3%E5%8F%B0" tabindex="-1">支持的平台</h1><p><a href="http://xn--WinSW-ex2i941ekshr71fjscu1y2ku.NET" target="_blank">WinSW需要运行在拥有.NET</a> Framework 4.6.1或者更新版本的Windows平台下</p><h1 id="%E4%B8%8B%E8%BD%BD" tabindex="-1">下载</h1><ul><li><p>github： <a href="https://github.com/winsw/winsw/releases" target="_blank">下载地址</a></p></li><li><p>百度网盘（v2.12.0）：<a href="https://pan.baidu.com/s/1mCdn2cLyKkA6BSuYtvA-1A?pwd=ktt7" target="_blank">WinSW-x86</a>  <a href="https://pan.baidu.com/s/1suXVU4I7v3mHApy5UQSO9g?pwd=f3i1" target="_blank">WinSW-x64</a></p></li></ul><h1 id="%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E" tabindex="-1">使用说明</h1><h2 id="%E5%85%A8%E5%B1%80%E5%BA%94%E7%94%A8" tabindex="-1">全局应用</h2><ol><li>获取<code>WinSW.exe</code>文件</li><li>编写<em>myapp.xml</em>文件（详细内容看[XML配置文件](# XML配置文件)）</li><li>运行<code>winsw install myapp.xml [options]</code>安装服务，使其写入系统服务中</li><li>运行<code>winsw start myapp.xml</code> 开启服务</li><li>运行<code>winsw status myapp.xml</code> 查看服务的运行状态</li></ol><h2 id="%E5%8D%95%E4%B8%80%E5%BA%94%E7%94%A8" tabindex="-1">单一应用</h2><ol><li>获取<code>WinSW.exe</code>文件并将其更名为你的服务名(例如<em>myapp.exe</em>).</li><li>编写<em>myapp.xml</em>文件</li><li>请确保前面两个文件在同一目录</li><li>运行<code>myapp.exe install [options]</code>安装服务，使其写入系统服务中</li><li>运行<code>myapp.exe start</code>开启服务</li><li>运行<code>myapp status myapp.xml</code> 查看服务的运行状态</li></ol><h1 id="%E5%91%BD%E4%BB%A4" tabindex="-1">命令</h1><p>除了使用说明中的<code>install</code>、<code>start</code>、<code>status</code>三个命令外，WinSW还提供了其他的命令，具体命令及说明如下：</p><ul><li><p>install：安装服务</p></li><li><p>uninstall：卸载服务</p></li><li><p>start：启动服务</p></li><li><p>stop：停止服务</p></li><li><p>restart：重启服务</p></li><li><p>status：检查服务状态</p></li><li><p>refresh：刷新服务属性</p></li><li><p>customize：自定义包装器可执行文件</p></li><li><p>dev：扩展命令（具体看下方）</p></li></ul><p>扩展命令：</p><ul><li><p>dev ps：绘制与服务相关的进程树</p></li><li><p>dev kill：如果服务停止响应，则终止该服务</p></li><li><p>dev list：列出当前可执行文件管理的服务</p></li></ul><h1 id="xml%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6" tabindex="-1">XML配置文件</h1><h2 id="%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84" tabindex="-1">文件结构</h2><p>xml文件的根元素必须是 <code>&lt;service&gt;</code>, 并支持以下的子元素</p><p>例子：</p><pre><code class="language-xml">&lt;service&gt;  &lt;id&gt;jenkins&lt;/id&gt;  &lt;name&gt;Jenkins&lt;/name&gt;  &lt;description&gt;This service runs Jenkins continuous integration system.&lt;/description&gt;  &lt;env name=&quot;JENKINS_HOME&quot; value=&quot;%BASE%&quot;/&gt;  &lt;executable&gt;java&lt;/executable&gt;  &lt;arguments&gt;-Xrs -Xmx256m -jar &quot;%BASE%\jenkins.war&quot; --httpPort=8080&lt;/arguments&gt;  &lt;log mode=&quot;roll&quot;&gt;&lt;/log&gt;&lt;/service&gt;</code></pre><h2 id="%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E6%89%A9%E5%B1%95" tabindex="-1">环境变量扩展</h2><p>配置 XML 文件可以包含 %Name% 形式的环境变量扩展。如果发现这种情况，将自动用变量的实际值替换。如果引用了未定义的环境变量，则不会进行替换。</p><p>此外，服务包装器还会自行设置环境变量 BASE，该变量指向包含重命名后的 WinSW.exe 的目录。这对引用同一目录中的其他文件非常有用。由于这本身就是一个环境变量，因此也可以从服务包装器启动的子进程中访问该值。</p><h2 id="%E9%85%8D%E7%BD%AE%E6%9D%A1%E7%9B%AE" tabindex="-1">配置条目</h2><h3 id="id" tabindex="-1">id</h3><p>必填 指定 Windows 内部用于标识服务的 ID。在系统中安装的所有服务中，该 ID 必须是唯一的，且应完全由字母数字字符组成。</p><pre><code class="language-xml">&lt;id&gt;jenkins&lt;/id&gt;</code></pre><h3 id="executable" tabindex="-1">executable</h3><p>必填 该元素指定要启动的可执行文件。它可以是绝对路径，也可以直接指定可执行文件的名称，然后从 PATH 中搜索（但要注意的是，服务通常以不同的用户账户运行，因此它的 PATH 可能与 shell 不同）。</p><pre><code class="language-xml">&lt;executable&gt;java&lt;/executable&gt;</code></pre><h3 id="name" tabindex="-1">name</h3><p>可选项 服务的简短显示名称，可以包含空格和其他字符。该名称不能太长，如 <id>，而且在给定系统的所有服务中也必须是唯一的。</p><pre><code class="language-xml">&lt;name&gt;Jenkins&lt;/name&gt;</code></pre><h3 id="description" tabindex="-1">description</h3><p>可选 对服务的长篇可读描述。当服务被选中时，它会显示在 Windows 服务管理器中。</p><pre><code class="language-xml">&lt;description&gt;This service runs Jenkins continuous integration system.&lt;/description&gt;</code></pre><h3 id="startmode" tabindex="-1">startmode</h3><p>可选 此元素指定 Windows 服务的启动模式。可以是以下值之一：自动或手动。有关详细信息，请参阅 [ChangeStartMode](<a href="https://learn.microsoft.com/zh-cn/windows/win32/cimwin32prov/changestartmode-method-in-class-win32-service" target="_blank">Win32_Service 类的 ChangeStartMode 方法 (CIMWin32 WMI 提供程序) - Win32 apps | Microsoft Learn</a>) 方法。默认值为自动<code>Automatic</code>。</p><h3 id="delayedautostart" tabindex="-1">delayedAutoStart</h3><p>可选 如果定义了<code>Automatic</code>模式，此布尔选项将启用延迟启动模式。更多信息，请参阅<a href="https://techcommunity.microsoft.com/t5/ask-the-performance-team/ws2008-startup-processes-and-delayed-automatic-start/ba-p/372692" target="_blank">Startup Processes and Delayed Automatic Start</a>。</p><p>请注意，该启动模式不适用于 Windows 7 和 Windows Server 2008 以上的旧版本。在这种情况下，Windows 服务安装可能会失败。</p><pre><code class="language-xml">&lt;delayedAutoStart&gt;true&lt;/delayedAutoStart&gt;</code></pre><h3 id="depend" tabindex="-1">depend</h3><p>可选 指定此服务依赖的其他服务的 ID。当服务 X 依赖于服务 Y 时，X 只能在 Y 运行时运行。</p><p>可使用多个元素指定多个依赖关系。</p><pre><code class="language-xml">&lt;depend&gt;Eventlog&lt;/depend&gt;&lt;depend&gt;W32Time&lt;/depend&gt;</code></pre><h3 id="log" tabindex="-1">log</h3><p>可选 用 <logpath> 和启动模式设置不同的日志目录：append（默认）、reset（清除日志）、ignore（忽略）、roll（移动到 *.old）。</p><p>更多信息，请参阅<a href="https://github.com/winsw/winsw/blob/v3/docs/logging-and-error-reporting.md" target="_blank">Logging and error reporting</a>。</p><pre><code class="language-xml">&lt;log mode=&quot;roll&quot;&gt;&lt;/log&gt;</code></pre><h3 id="arguments" tabindex="-1">arguments</h3><p>可选 <arguments> 元素指定要传递给可执行文件的参数。</p><pre><code class="language-xml">&lt;arguments&gt;arg1 arg2 arg3&lt;/arguments&gt;</code></pre><p>或者</p><pre><code class="language-xml">&lt;arguments&gt;  arg1  arg2  arg3&lt;/arguments&gt;</code></pre><h3 id="stopargument%2Fstopexecutable" tabindex="-1">stopargument/stopexecutable</h3><p>可选 当服务被请求停止时，winsw 会简单地调用 <a href="https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess" target="_blank">TerminateProcess function</a>立即杀死服务。但是，如果存在 <stoparguments> 元素，winsw 将使用指定的参数启动另一个 <executable> 进程（或 <stopopexecutable>，如果已指定），并期望该进程启动服务进程的优雅关闭。</p><p>然后，Winsw 将等待这两个进程自行退出，然后向 Windows 报告服务已终止。</p><p>使用 <stoparguments> 时，必须使用 <startarguments> 而不是 <arguments>。</p><pre><code class="language-xml">&lt;executable&gt;catalina.sh&lt;/executable&gt;&lt;startarguments&gt;jpda run&lt;/startarguments&gt;&lt;stopexecutable&gt;catalina.sh&lt;/stopexecutable&gt;&lt;stoparguments&gt;stop&lt;/stoparguments&gt;</code></pre><h3 id="additional-commands" tabindex="-1">Additional commands</h3><p>扩展命令包括<code>prestart</code>,<code>poststart</code>,<code>prestop</code>,<code>poststop</code>四个，以<code>prestart</code>为例写法如下：</p><pre><code class="language-xml">&lt;prestart&gt;  &lt;executable&gt;&lt;/executable&gt;  &lt;arguments&gt;&lt;/arguments&gt;  &lt;stdoutPath&gt;&lt;/stdoutPath&gt;  &lt;stderrPath&gt;&lt;/stderrPath&gt;&lt;/prestart&gt;</code></pre><ul><li><p>prestart：在服务启动时、主进程启动前执行</p></li><li><p>poststart：在服务启动时和主程序启动后执行</p></li><li><p>prestop：在服务停止时、主进程停止前执行</p></li><li><p>poststop：在服务停止时和主进程停止后执行</p></li></ul><p>共用的命令如下：</p><ul><li><p>stdoutPath：指定将标准输出重定向到的路径</p></li><li><p>stderrPath：指定将标准错误输出重定向到的路径</p></li></ul><p>在 stdoutPath 或 stderrPath 中指定 NUL 可处理相应的数据流</p><h3 id="preshutdown" tabindex="-1">preshutdown</h3><p>当系统关闭时，让服务有更多时间停止。</p><p>系统默认的预关机超时时间为三分钟。</p><pre><code class="language-xml">&lt;preshutdown&gt;false&lt;/preshutdown&gt;&lt;preshutdownTimeout&gt;3 min&lt;/preshutdown&gt;</code></pre><h3 id="stoptimeout" tabindex="-1">stoptimeout</h3><p>当服务被请求停止时，winsw 会首先尝试向控制台应用程序发送 Ctrl+C 信号，或向 Windows 应用程序发布关闭消息，然后等待长达 15 秒的时间，让进程自己优雅地退出。如果超时或无法发送信号或消息，winsw 就会立即终止服务。</p><p>通过这个可选元素，您可以更改 &quot;15 秒 &quot;的值，这样就可以控制 winsw 让服务自行关闭的时间。</p><pre><code class="language-xml">&lt;stoptimeout&gt;10sec&lt;/stoptimeout&gt;</code></pre><h3 id="environment" tabindex="-1">Environment</h3><p>如有必要，可多次指定该可选元素，以指定要为子进程设置的环境变量。</p><pre><code class="language-xml">&lt;env name=&quot;HOME&quot; value=&quot;c:\abc&quot; /&gt;</code></pre><h3 id="interactive" tabindex="-1">interactive</h3><p>如果指定了此可选元素，则允许服务与桌面交互，如显示新窗口和对话框。</p><pre><code class="language-xml">&lt;interactive&gt;true&lt;/interactive&gt;</code></pre><p>请注意，自引入 UAC（Windows Vista 及以后版本）以来，服务已不再真正允许与桌面交互。在这些操作系统中，这样做的目的只是让用户切换到一个单独的窗口站来与服务交互。</p><h3 id="beeponshutdown" tabindex="-1">beeponshutdown</h3><p>该可选元素用于在服务关闭时发出<a href="https://docs.microsoft.com/windows/win32/api/utilapiset/nf-utilapiset-beep" target="_blank">simple tones</a>。此功能只能用于调试，因为某些操作系统和硬件不支持此功能。</p><pre><code class="language-xml">&lt;beeponshutdown&gt;true&lt;/beeponshutdown&gt;</code></pre><h3 id="download" tabindex="-1">download</h3><p>可以多次指定这个可选元素，以便让服务包装器从 URL 获取资源并将其作为文件放到本地。此操作在服务启动时，即 <code>&lt;executable&gt;</code> 指定的应用程序启动前运行。</p><p>对于需要身份验证的服务器，必须根据身份验证类型指定一些参数。只有基本身份验证需要额外的子参数。支持的身份验证类型有</p><ul><li><p>none（无）：默认值，不得指定</p></li><li><p>sspi：Windows <a href="https://docs.microsoft.com/windows/win32/secauthn/sspi" target="_blank">Security Support Provider Interface</a>，包括 Kerberos、NTLM 等。</p></li><li><p>basic：基本身份验证，子参数：</p><ul><li><p>user=“UserName” 用户名</p></li><li><p>password=“Passw0rd”</p></li><li><p>unsecureAuth=“true”: 默认值=“false”</p></li></ul></li></ul><p>参数 unsecureAuth 仅在传输协议为 HTTP（未加密数据传输）时有效。这是一个安全漏洞，因为凭据是以明文发送的！对于 SSPI 身份验证来说，这并不重要，因为身份验证令牌是加密的。</p><p>对于使用 HTTPS 传输协议的目标服务器来说，颁发服务器证书的 CA 必须得到客户端的信任。当服务器位于互联网上时，通常会出现这种情况。当一个组织在内部网中使用自行签发的 CA 时，情况可能并非如此。在这种情况下，有必要将 CA 导入 Windows 客户端的证书 MMC。请参阅 &quot;<a href="https://docs.microsoft.com/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc754841(v=ws.11)" target="_blank">Manage Trusted Root Certificates</a>&quot;中的说明。必须将自行签发的 CA 导入计算机的可信根证书颁发机构。</p><p>默认情况下，如果操作失败（如从不可用），下载命令不会导致服务启动失败。为了在这种情况下强制下载失败，可以指定 failOnError 布尔属性。</p><p>要指定自定义代理，请使用参数 proxy，格式如下：</p><ul><li><p>有凭据：<a href="http://USERNAME:PASSWORD@HOST" target="_blank">http://USERNAME:PASSWORD@HOST</a>:PORT/。</p></li><li><p>无凭据： <a href="http://HOST" target="_blank">http://HOST</a>:PORT/。</p></li></ul><pre><code class="language-xml">&lt;download from=&quot;http://example.com/some.dat&quot; to=&quot;%BASE%\some.dat&quot; /&gt;&lt;download from=&quot;http://example.com/some.dat&quot; to=&quot;%BASE%\some.dat&quot; failOnError=&quot;true&quot;/&gt;&lt;download from=&quot;http://example.com/some.dat&quot; to=&quot;%BASE%\some.dat&quot; proxy=&quot;http://192.168.1.5:80/&quot;/&gt;&lt;download from=&quot;https://example.com/some.dat&quot; to=&quot;%BASE%\some.dat&quot; auth=&quot;sspi&quot; /&gt;&lt;download from=&quot;https://example.com/some.dat&quot; to=&quot;%BASE%\some.dat&quot; failOnError=&quot;true&quot;          auth=&quot;basic&quot; user=&quot;aUser&quot; password=&quot;aPassw0rd&quot; /&gt;&lt;download from=&quot;http://example.com/some.dat&quot; to=&quot;%BASE%\some.dat&quot;          proxy=&quot;http://aUser:aPassw0rd@192.168.1.5:80/&quot;          auth=&quot;basic&quot; unsecureAuth=&quot;true&quot;          user=&quot;aUser&quot; password=&quot;aPassw0rd&quot; /&gt;</code></pre><p>这是开发自我更新服务的另一个有用的组成部分。</p><p>自 2.7 版起，如果目标文件存在，WinSW 将在 If-Modified-Since 标头中发送其最后写入时间，如果收到 304 Not Modified，则跳过下载。</p><h3 id="onfailure" tabindex="-1">onfailure</h3><p>当 winsw 启动的进程失败（即以非零退出代码退出）时，这个可选的可重复元素将控制其行为。</p><pre><code class="language-xml">&lt;onfailure action=&quot;restart&quot; delay=&quot;10 sec&quot;/&gt;&lt;onfailure action=&quot;restart&quot; delay=&quot;20 sec&quot;/&gt;&lt;onfailure action=&quot;reboot&quot; /&gt;</code></pre><p>例如，上述配置会导致服务在第一次故障后 10 秒内重新启动，在第二次故障后 20 秒内重新启动，然后如果服务再次发生故障，Windows 将重新启动。</p><p>每个元素都包含一个强制的 action 属性和可选的 delay 属性，前者用于控制 Windows SCM 将采取的行动，后者用于控制采取该行动前的延迟时间。action 的合法值为</p><ul><li><p>restart：重新启动服务</p></li><li><p>reboot：重新启动 Windows。将显示带有 <a href="https://docs.microsoft.com/windows-hardware/drivers/debugger/bug-check-0xef--critical-process-died" target="_blank">CRITICAL_PROCESS_DIED</a> 错误检查代码的蓝色屏幕</p></li><li><p>none：不执行任何操作，让服务停止延迟属性的可能后缀为秒/秒/分钟/分钟/小时/小时/天/天。如果缺少，延迟属性默认为 0。</p></li></ul><p>延迟属性的后缀可能是秒/秒/分/分/小时/小时/天/天。如果缺少，延迟属性默认为 0。</p><p>如果服务不断发生故障，并且超过了配置的 <code>&lt;onfailure&gt; </code>次数，则会重复上次的操作。因此，如果只想始终自动重启服务，只需像这样指定一个 <code>&lt;onfailure&gt;</code> 元素即可：</p><pre><code class="language-xml">&lt;onfailure action=&quot;restart&quot; /&gt;</code></pre><h3 id="resetfailure" tabindex="-1">resetfailure</h3><p>此可选元素控制 Windows SCM 重置故障计数的时间。例如，如果您指定 <resetfailure>1 小时</resetfailure>，而服务持续运行的时间超过一小时，那么故障计数将重置为零。</p><p>换句话说，这是您认为服务成功运行的持续时间。默认为 1 天。</p><pre><code class="language-xml">&lt;resetfailure&gt;1 hour&lt;/resetfailure&gt;</code></pre><h3 id="security-descriptor" tabindex="-1">Security descriptor</h3><p>SDDL 格式的服务安全描述符字符串。</p><p>有关详细信息，请参阅<a href="https://learn.microsoft.com/zh-cn/windows/win32/secauthz/security-descriptor-definition-language" target="_blank">安全描述符定义语言</a>。</p><pre><code class="language-xml">&lt;securityDescriptor&gt;&lt;/securityDescriptor&gt;</code></pre><h3 id="service-account" tabindex="-1">Service account</h3><p>服务默认安装为 <a href="https://docs.microsoft.com/windows/win32/services/localsystem-account" target="_blank">LocalSystem 账户</a>。如果您的服务不需要很高的权限级别，可以考虑使用 <a href="https://docs.microsoft.com/windows/win32/services/localservice-account" target="_blank">LocalService 账户</a>、<a href="https://docs.microsoft.com/windows/win32/services/networkservice-account" target="_blank">NetworkService 帐户</a>或用户账户。</p><p>要使用用户账户，请像这样指定 <code>&lt;serviceaccount&gt;</code> 元素：</p><pre><code class="language-xml">&lt;serviceaccount&gt;  &lt;username&gt;DomainName\UserName&lt;/username&gt;  &lt;password&gt;Pa55w0rd&lt;/password&gt;  &lt;allowservicelogon&gt;true&lt;/allowservicelogon&gt;&lt;/serviceaccount&gt;</code></pre><p><code>&lt;username&gt; </code>的格式为 DomainName\UserName 或 UserName@DomainName。如果账户属于内置域，则可以指定 .\UserName。</p><p><code>&lt;allowservicelogon&gt;</code> 是可选项。如果设置为 true，将自动为列出的账户设置 &quot;允许以服务身份登录 &quot;的权限。</p><p>要使用<a href="https://docs.microsoft.com/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview" target="_blank">Group Managed Service Accounts Overview</a>，请在账户名后追加 $ 并删除 <code>&lt;password&gt;</code> 元素：</p><pre><code class="language-xml">&lt;serviceaccount&gt;  &lt;username&gt;DomainName\GmsaUserName$&lt;/username&gt;  &lt;allowservicelogon&gt;true&lt;/allowservicelogon&gt;&lt;/serviceaccount&gt;</code></pre><h4 id="localsystem-account" tabindex="-1">LocalSystem account</h4><p>要明确使用<a href="https://docs.microsoft.com/windows/win32/services/localsystem-account" target="_blank">LocalSystem 帐户</a> ，请指定以下内容：</p><pre><code class="language-xml">&lt;serviceaccount&gt;  &lt;username&gt;LocalSystem&lt;/username&gt;&lt;/serviceaccount&gt;</code></pre><p>请注意，该账户没有密码，因此提供的任何密码都将被忽略。</p><h4 id="localservice-account" tabindex="-1">LocalService account</h4><p>要使用 <a href="https://docs.microsoft.com/windows/win32/services/localservice-account" target="_blank">LocalService 帐户</a>，请指定以下内容：</p><pre><code class="language-xml">&lt;serviceaccount&gt;  &lt;username&gt;NT AUTHORITY\LocalService&lt;/username&gt;&lt;/serviceaccount&gt;</code></pre><p>请注意，该账户没有密码，因此提供的任何密码都将被忽略。</p><h4 id="networkservice-account" tabindex="-1">NetworkService account</h4><p>要使用 <a href="https://docs.microsoft.com/windows/win32/services/networkservice-account" target="_blank">NetworkService 帐户</a>，请指定以下内容：</p><pre><code class="language-xml">&lt;serviceaccount&gt;  &lt;username&gt;NT AUTHORITY\NetworkService&lt;/username&gt;&lt;/serviceaccount&gt;</code></pre><p>请注意，该账户没有密码，因此提供的任何密码都将被忽略。</p><h4 id="prompt" tabindex="-1">prompt</h4><p>可选。提示输入用户名和密码。</p><pre><code class="language-xml">&lt;serviceaccount&gt;  &lt;prompt&gt;dialog|console&lt;/prompt&gt;&lt;/serviceaccount&gt;</code></pre><ul><li><p>话框：使用对话框进行提示。</p></li><li><p>控制台：在控制台进行提示。</p></li></ul><h3 id="working-directory" tabindex="-1">Working directory</h3><p>某些服务在运行时需要指定工作目录。为此，请像这样指定<code>&lt;workingdirectory&gt;</code>元素：</p><pre><code class="language-xml">&lt;workingdirectory&gt;C:\application&lt;/workingdirectory&gt;</code></pre><h3 id="priority" tabindex="-1">Priority</h3><p>可选择指定服务进程的调度优先级（相当于 Unix nice），可选值包括<code>idle</code>, <code>belownormal</code>, <code>normal</code>, <code>abovenormal</code>, <code>high</code>, <code>realtime</code>（不区分大小写）。</p><pre><code class="language-xml">&lt;priority&gt;idle&lt;/priority&gt;</code></pre><p>指定高于正常值的优先级会产生意想不到的后果。有关详细信息，请参阅 .NET 文档中的 <a href="https://docs.microsoft.com/dotnet/api/system.diagnostics.processpriorityclass" target="_blank">ProcessPriorityClass 枚举</a>。此功能的主要目的是以较低的优先级启动进程，以免干扰计算机的交互式使用。</p><h3 id="auto-refresh" tabindex="-1">Auto refresh</h3><pre><code class="language-xml">&lt;autoRefresh&gt;true&lt;/autoRefresh&gt;</code></pre><p>当服务启动或执行以下命令时，自动刷新服务属性：</p><ul><li>start</li><li>stop</li><li>restart</li></ul><p>默认值为 true。</p><h3 id="shareddirectorymapping" tabindex="-1">sharedDirectoryMapping</h3><p>默认情况下，即使在 Windows 服务配置文件中进行了配置，Windows 也不会为服务建立共享驱动器映射。由于域策略的原因，有时无法解决这个问题。</p><p>这样就可以在启动可执行文件之前映射外部共享目录。</p><pre><code class="language-xml">&lt;sharedDirectoryMapping&gt;  &lt;map label=&quot;N:&quot; uncpath=&quot;\\UNC&quot; /&gt;  &lt;map label=&quot;M:&quot; uncpath=&quot;\\UNC2&quot; /&gt;&lt;/sharedDirectoryMapping&gt;</code></pre>]]>
                    </description>
                    <pubDate>Tue, 17 Oct 2023 17:54:54 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[解决Java应用中的字符编码问题：深入理解JVM编码格式]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/jie-jue-java-ying-yong-zhong-de-zi-fu-bian-ma-wen-ti--shen-ru-li-jie-jvm-bian-ma-ge-shi</link>
                    <description>
                            <![CDATA[<h1 id="%E5%AF%BC%E8%A8%80" tabindex="-1">导言</h1><p>在Java应用程序开发中，字符编码问题是一个常见的挑战。正确处理字符编码对于数据的完整性至关重要。本文将深入探讨JVM（Java虚拟机）编码格式的相关内容，包括如何查询、设置和修改，以及如何应对字符编码问题。</p><h1 id="1%E3%80%81jvm%E7%BC%96%E7%A0%81%E6%A0%BC%E5%BC%8F%E7%AE%80%E4%BB%8B%EF%BC%9A" tabindex="-1">1、JVM编码格式简介：</h1><p>JVM（Java虚拟机）是运行Java程序的核心组件，它负责将Java字节码转换为机器指令。在Java应用程序中，正确的编码设置非常重要，因为它直接影响到字符串的处理和输出。了解JVM的编码格式以及如何设置和管理它们对于开发可靠和可移植的Java应用程序至关重要。</p><h1 id="2%E3%80%81%E6%9F%A5%E8%AF%A2jvm%E7%9A%84%E7%BC%96%E7%A0%81%E6%A0%BC%E5%BC%8F%EF%BC%9A" tabindex="-1">2、查询JVM的编码格式：</h1><p>有多种方法可以查询JVM的编码格式。其中一种方法是使用Java代码来查询。通过调用<code>System.getProperty(&quot;file.encoding&quot;)</code>方法，可以获取JVM当前的默认编码格式。另一种方法是使用命令行工具查看JVM的编码设置。可以使用以下命令来查看JVM参数：</p><pre><code class="language-shell">java -XX:+PrintFlagsFinal -version | grep -iE &#39;Default Charset&#39;</code></pre><h1 id="3%E3%80%81%E8%AE%BE%E7%BD%AEjvm%E7%9A%84%E7%BC%96%E7%A0%81%E6%A0%BC%E5%BC%8F%EF%BC%9A" tabindex="-1">3、设置JVM的编码格式：</h1><p>有两种主要方法可以配置JVM的编码格式。第一种是通过启动参数配置。在启动Java应用程序时，可以在命令行或脚本中添加特定的启动参数来设置JVM的编码格式。例如，使用以下命令来设置UTF-8编码：</p><pre><code class="language-shell">java -Dfile.encoding=UTF-8 YourApplication</code></pre><p>第二种方法是使用Java代码修改系统属性以设置JVM编码。可以通过调用<code>System.setProperty(&quot;file.encoding&quot;, &quot;UTF-8&quot;)</code>方法来实现。确保在应用程序的适当位置执行此操作以确保编码在整个生命周期中保持一致。</p><h1 id="4%E3%80%81%E6%94%B9jvm%E7%9A%84%E9%BB%98%E8%AE%A4%E7%BC%96%E7%A0%81%E6%A0%BC%E5%BC%8F%EF%BC%9A" tabindex="-1">4、改JVM的默认编码格式：</h1><ul><li>编辑启动脚本以调整JVM编码设置。找到启动Java应用程序的脚本文件（如<code>startup.sh</code>或<code>startup.bat</code>），并添加适当的启动参数来指定所需的编码格式。例如，使用以下命令来设置UTF-8编码：</li></ul><pre><code class="language-shell">export JAVA_OPTS=&quot;-Dfile.encoding=UTF-8&quot;</code></pre><p>然后重新启动应用程序，新的编码设置将生效。</p><ul><li>如果使用的是IDE（集成开发环境），可以在项目设置或运行配置中添加相应的启动参数来修改JVM的默认编码格式。</li><li>如果应用程序已经部署在服务器上，可以通过修改服务器配置文件或环境变量来更改默认编码格式。这通常涉及到对操作系统的环境变量进行设置或更新相关的服务配置。</li></ul><h1 id="5%E3%80%81%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%E5%92%8C%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9A" tabindex="-1">5、注意事项和最佳实践：</h1><p>在选择和设置JVM的编码格式时，需要注意以下几点：</p><ul><li>确保选择与应用程序需求相匹配的编码格式。不同的字符集适用于不同的场景，如处理文本数据、网络通信等。根据具体的应用场景选择合适的编码格式可以提高程序的效率和正确性。</li><li>理解特定应用程序的字符编码要求。某些应用程序可能有特定的字符编码要求，如XML解析器可能要求使用特定的字符集。在设计和开发应用程序时，要仔细考虑这些要求，并相应地设置JVM的编码格式。</li><li>注意跨平台兼容性。不同的操作系统和Java版本可能支持不同的字符集。在进行跨平台开发时，要测试和验证所选的编码</li></ul><h1 id="%E7%BB%93%E8%AE%BA" tabindex="-1">结论</h1><p>正确设置JVM编码格式对于Java应用程序至关重要，因为它直接影响字符数据的传输和处理。通过本文提供的详细指南，您可以解决字符编码问题，确保应用程序在各种情况下都能正确运行。不要低估字符编码的重要性，因为它可能对您的应用程序的可靠性产生深远影响。</p>]]>
                    </description>
                    <pubDate>Tue, 10 Oct 2023 18:20:31 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[深入了解 Cron 时间字段：定时任务的精确控制]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/shen-ru-le-jie-cron-shi-jian-zi-duan--ding-shi-ren-wu-de-jing-que-kong-zhi</link>
                    <description>
                            <![CDATA[<p>在 Linux 和 Unix 系统中，cron 是一个强大的工具，用于执行预定时间的任务。Cron 允许用户自动化各种重复性任务，如备份、系统监控、日志清理等。在<br />cron 中，时间的设定是至关重要的，它使用一些特殊的时间字段来确定任务的执行时机。本文将深入探讨常见的 cron 时间字段及其用途。</p><h1 id="1%E3%80%81%E5%B8%B8%E8%A7%84-cron-%E6%97%B6%E9%97%B4%E5%AD%97%E6%AE%B5" tabindex="-1">1、常规 Cron 时间字段</h1><p>常规 Cron 时间字段：精确控制任务执行时间</p><p>在常规 cron 时间字段中，您可以通过分钟、小时、日期等来精确控制任务的执行时间。以下是一些示例：</p><h2 id="1.1%E3%80%81%E6%AF%8F%E5%A4%A9%E5%87%8C%E6%99%A8%E6%89%A7%E8%A1%8C%E5%A4%87%E4%BB%BD%E4%BB%BB%E5%8A%A1" tabindex="-1">1.1、每天凌晨执行备份任务</h2><pre><code class="language-">0 0 * * * /usr/local/bin/backup.sh</code></pre><h2 id="1.2%E3%80%81%E6%AF%8F%E5%B0%8F%E6%97%B6%E6%89%A7%E8%A1%8C%E7%B3%BB%E7%BB%9F%E7%9B%91%E6%8E%A7%E4%BB%BB%E5%8A%A1" tabindex="-1">1.2、每小时执行系统监控任务</h2><pre><code class="language-">0 * * * * /usr/local/bin/system_monitor.sh</code></pre><h2 id="1.3%E3%80%81%E6%AF%8F%E5%91%A8%E6%89%A7%E8%A1%8C%E6%97%A5%E5%BF%97%E6%B8%85%E7%90%86%E4%BB%BB%E5%8A%A1%EF%BC%9A" tabindex="-1">1.3、每周执行日志清理任务：</h2><pre><code class="language-">0 2 * * 6 /usr/local/bin/clean_logs.sh</code></pre><h2 id="1.4%E3%80%81%E6%AF%8F%E6%9C%88%E6%89%A7%E8%A1%8C%E7%B3%BB%E7%BB%9F%E6%9B%B4%E6%96%B0%E4%BB%BB%E5%8A%A1%EF%BC%9A" tabindex="-1">1.4、每月执行系统更新任务：</h2><pre><code class="language-">0 3 1 * * /usr/bin/apt-get update &amp;&amp; /usr/bin/apt-get upgrade -y</code></pre><h2 id="1.5%E3%80%81%E6%AF%8F%E9%9A%94-15-%E5%88%86%E9%92%9F%E6%89%A7%E8%A1%8C%E6%A3%80%E6%9F%A5%E7%BD%91%E7%AB%99%E5%8F%AF%E7%94%A8%E6%80%A7%E4%BB%BB%E5%8A%A1%EF%BC%9A" tabindex="-1">1.5、每隔 15 分钟执行检查网站可用性任务：</h2><pre><code class="language-">*/15 * * * * /usr/local/bin/check_website.sh</code></pre><p>这些常规的 cron 时间字段允许您按照特定的时间表来安排任务的执行，非常适用于各种自动化需求。</p><h1 id="2%E3%80%81%E7%89%B9%E6%AE%8A-cron-%E6%97%B6%E9%97%B4%E5%AD%97%E6%AE%B5%EF%BC%9A%E7%AE%80%E5%8C%96%E6%97%B6%E9%97%B4%E8%AE%BE%E5%AE%9A" tabindex="-1">2、特殊 Cron 时间字段：简化时间设定</h1><p>除了常规的时间字段外，还有一些特殊的时间字段，如 @reboot、@yearly、@monthly 等，它们可以更方便地设置任务的执行时间，通常用于特殊场景。示例：</p><h2 id="2.1%E3%80%81%40reboot%EF%BC%9A%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%97%B6%E6%89%A7%E8%A1%8C%E4%BB%BB%E5%8A%A1" tabindex="-1">2.1、@reboot：系统启动时执行任务</h2><pre><code class="language-">@reboot /usr/local/bin/startup_script.sh</code></pre><h2 id="2.2%E3%80%81%40yearly-%E6%88%96-%40annually%EF%BC%9A%E6%AF%8F%E5%B9%B4%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1" tabindex="-1">2.2、@yearly 或 @annually：每年执行一次</h2><pre><code class="language-">@yearly /usr/local/bin/yearly_task.sh</code></pre><h2 id="2.3%E3%80%81%40monthly%EF%BC%9A%E6%AF%8F%E6%9C%88%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1" tabindex="-1">2.3、@monthly：每月执行一次</h2><pre><code class="language-">@monthly /usr/local/bin/monthly_task.sh</code></pre><h2 id="2.4%E3%80%81%40weekly%EF%BC%9A%E6%AF%8F%E5%91%A8%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1" tabindex="-1">2.4、@weekly：每周执行一次</h2><pre><code class="language-">@weekly /usr/local/bin/weekly_task.sh</code></pre><h2 id="2.5%E3%80%81%40daily-%E6%88%96-%40midnight%EF%BC%9A%E6%AF%8F%E5%A4%A9%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1" tabindex="-1">2.5、@daily 或 @midnight：每天执行一次</h2><pre><code class="language-">@daily /usr/local/bin/daily_task.sh</code></pre><h2 id="2.6%E3%80%81%40hourly%EF%BC%9A%E6%AF%8F%E5%B0%8F%E6%97%B6%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1" tabindex="-1">2.6、@hourly：每小时执行一次</h2><pre><code class="language-">@hourly /usr/local/bin/hourly_task.sh</code></pre><p>这些特殊的时间字段使得在 crontab 中定义定时任务更加方便，您可以根据任务的周期性要求选择适当的时间字段。它们使时间设定更加直观和易读，而不需要编写复杂的时间表。通过合理利用cron 时间字段，您可以轻松自动化各种系统维护和管理任务，提高系统的效率和可靠性。</p>]]>
                    </description>
                    <pubDate>Fri, 15 Sep 2023 11:17:56 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[解决vue图片不刷新问题：浏览器缓存与缓存控制头的终极对决]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/jie-jue-vue-tu-pian-bu-shua-xin-wen-ti--liu-lan-qi-huan-cun-yu-huan-cun-kong-zhi-tou-de-zhong-ji-dui-jue</link>
                    <description>
                            <![CDATA[<p>在现代Web开发中，许多开发者都曾经遇到过一个令人困扰的问题：当图片URL没有变化但图片内容却发生了变化时，浏览器似乎不会主动刷新图片，从而导致显示旧的内容。这个问题在网站和应用中的图片更新时尤为突出，可能会影响用户体验和页面正确性。</p><p>在这篇博客文章中，我们将探讨这个问题，并提供多种解决方案，其中包括添加时间戳或随机参数以绕过浏览器缓存以及配置缓存控制头来告诉浏览器如何处理这些图片。我们将深入了解这些解决方案的实现方式以及它们在不同服务器和框架中的应用。</p><h1 id="%E9%97%AE%E9%A2%98%E7%9A%84%E6%A0%B9%E6%BA%90" tabindex="-1">问题的根源</h1><p>问题的根本在于浏览器的缓存机制。浏览器会根据图片的URL来决定是否重新请求图片或者使用缓存中的版本。当图片的URL保持不变时，浏览器会倾向于使用已经缓存的旧版本，而不会去服务器重新获取新的图片内容。</p><h1 id="%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E4%B8%80%EF%BC%9A%E6%B7%BB%E5%8A%A0%E6%97%B6%E9%97%B4%E6%88%B3%E6%88%96%E9%9A%8F%E6%9C%BA%E5%8F%82%E6%95%B0" tabindex="-1">解决方案一：添加时间戳或随机参数</h1><p>为了绕过浏览器的缓存机制，最简单的方法之一是在图片的URL上添加一个时间戳或随机参数。这将使每次请求都看起来像一个不同的URL，从而迫使浏览器重新加载图片。</p><pre><code class="language-html">&lt;img :src=&quot;&#39;your-image-url.jpg?&#39; + Date.now()&quot;&gt;</code></pre><p>或者使用JavaScript生成随机参数：</p><pre><code class="language-html">&lt;img :src=&quot;&#39;your-image-url.jpg?&#39; + Math.random()&quot;&gt;</code></pre><p>这种方法适用于各种Web开发环境，并且非常容易实现。</p><h1 id="%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E4%BA%8C%EF%BC%9A%E9%85%8D%E7%BD%AE%E7%BC%93%E5%AD%98%E6%8E%A7%E5%88%B6%E5%A4%B4" tabindex="-1">解决方案二：配置缓存控制头</h1><p>另一种更强大的方法是在服务器端配置缓存控制头。不同的服务器和框架有不同的配置方式，以下是一些示例：</p><h2 id="apache" tabindex="-1">Apache</h2><p>在Apache服务器上，您可以通过<code>.htaccess</code>文件来配置缓存控制头，告诉浏览器不要缓存特定类型的图片。</p><pre><code class="language-apacheconf">&lt;IfModule mod_headers.c&gt;    # 禁止缓存指定文件类型的图片，例如 .jpg 和 .png    &lt;FilesMatch &quot;\.(jpg|png)$&quot;&gt;        Header set Cache-Control &quot;no-cache, no-store, must-revalidate&quot;        Header set Pragma &quot;no-cache&quot;        Header set Expires 0    &lt;/FilesMatch&gt;&lt;/IfModule&gt;</code></pre><h2 id="nginx" tabindex="-1">Nginx</h2><p>如果使用Nginx作为服务器，可以在Nginx配置文件中添加以下配置来实现缓存控制：</p><pre><code class="language-nginx">location ~* \.(jpg|png)$ {    expires -1;    add_header Cache-Control &quot;no-store, no-cache, must-revalidate, max-age=0&quot;;    add_header Pragma &quot;no-cache&quot;;}</code></pre><h2 id="node.js%EF%BC%88express%E6%A1%86%E6%9E%B6%EF%BC%89" tabindex="-1">Node.js（Express框架）</h2><p>在Node.js中使用Express框架，您可以创建一个中间件来设置缓存控制头，以确保浏览器不会缓存特定类型的图片。</p><pre><code class="language-javascript">const express = require(&#39;express&#39;);const app = express();// 禁止缓存指定文件类型的图片，例如 .jpg 和 .pngapp.use((req, res, next) =&gt; {    if (req.url.endsWith(&#39;.jpg&#39;) || req.url.endsWith(&#39;.png&#39;)) {        res.setHeader(&#39;Cache-Control&#39;, &#39;no-store, no-cache, must-revalidate, max-age=0&#39;);        res.setHeader(&#39;Pragma&#39;, &#39;no-cache&#39;);    }    next();});// 其他路由和中间件设置app.listen(3000, () =&gt; {    console.log(&#39;Server is running on port 3000&#39;);});</code></pre><h1 id="%E7%BB%93%E8%AE%BA" tabindex="-1">结论</h1><p>无论您选择哪种方法，解决图片不刷新的问题都是可能的。添加时间戳或随机参数是最简单的方法之一，但它可能需要在多个地方修改代码。配置缓存控制头则可以更全面地控制缓存行为，但需要在服务器端进行配置。</p><p>根据您的项目需求和服务器环境，选择适合您的方法，并确保您的用户可以始终看到最新的图片内容，以提供更好的用户体验。希望本文对您有所帮助，解决了这个常见的开发问题。</p>]]>
                    </description>
                    <pubDate>Tue, 12 Sep 2023 11:36:20 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[选择合适的帧率和分辨率：优化RTSP流视频抓取]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/xuan-ze-he-shi-de-zhen-lv-he-fen-bian-lv--you-hua-rtsp-liu-shi-pin-zhua-qu</link>
                    <description>
                            <![CDATA[<p>最终，了解您的应用需求并进行测试是选择合适的帧率和分辨率的关键。通过仔细权衡这些因素，您可以确保您的RTSP流视频抓取应用提供了所需的性能和图像质量。</p><h1 id="%E5%BC%95%E8%A8%80" tabindex="-1">引言</h1><p>在实时视频流应用中，选择适当的帧率和分辨率对于确保视频流的顺畅播放和图像质量至关重要。本文将向您介绍如何使用Java和JavaCV库中的FFmpegFrameGrabber来从RTSP流中抓取图像，并在抓取时设置帧率和分辨率。</p><h1 id="%E4%B8%80%E3%80%81%E9%85%8D%E7%BD%AE%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83" tabindex="-1">一、配置开发环境</h1><p>首先，确保您的Java项目中包含JavaCV库的依赖。您可以在Maven项目中添加以下依赖：</p><pre><code class="language-xml">&lt;dependency&gt;    &lt;groupId&gt;org.bytedeco&lt;/groupId&gt;    &lt;artifactId&gt;javacv-platform&lt;/artifactId&gt;    &lt;version&gt;1.5.1&lt;/version&gt; &lt;!-- 请检查最新版本 --&gt;&lt;/dependency&gt;</code></pre><h1 id="%E4%BA%8C%E3%80%81%E4%BD%BF%E7%94%A8java%E4%BB%A3%E7%A0%81%E6%8A%93%E5%8F%96rtsp%E6%B5%81%E5%9B%BE%E5%83%8F" tabindex="-1">二、使用Java代码抓取RTSP流图像</h1><p>下面是一个示例Java代码，演示了如何使用FFmpegFrameGrabber从RTSP流中抓取图像并将其保存为JPEG格式的图像文件。</p><pre><code class="language-java">import org.bytedeco.javacv.FFmpegFrameGrabber;import org.bytedeco.javacv.Frame;import org.bytedeco.javacv.Java2DFrameConverter;import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.File;public class RTSPImageCapture {    public static void main(String[] args) {        String rtsp = &quot;YOUR_RTSP_URL_HERE&quot;; // 替换为实际的RTSP URL        String imgSrc = &quot;&quot;; // 图像保存路径        String linuxImg = &quot;/path/to/linux/img/&quot;; // Linux系统下的保存路径        String winImg = &quot;C:\\path\\to\\windows\\img\\&quot;; // Windows系统下的保存路径        try {            FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(rtsp);            // 使用tcp的方式，不然会丢包很严重            grabber.setOption(&quot;rtsp_transport&quot;, &quot;tcp&quot;);            grabber.start();            Frame frame = grabber.grabImage();            if (frame != null) {                if (imgSrc == null || imgSrc.isEmpty()) {                    String path = &quot;&quot;;                    if (SystemUtils.isLinux()) {                        path = linuxImg;                    } else if (SystemUtils.isWindows()) {                        path = winImg;                    }                    imgSrc = path + &quot;video.jpg&quot;;                }                File file = new File(imgSrc);                file.createNewFile();                Java2DFrameConverter converter = new Java2DFrameConverter();                BufferedImage bufferedImage = converter.getBufferedImage(frame);                ImageIO.write(bufferedImage, &quot;jpg&quot;, file);            }            grabber.stop();        } catch (Exception e) {            e.printStackTrace();        }    }}</code></pre><p>确保将上述代码中的<code>YOUR_RTSP_URL_HERE</code>替换为实际的RTSP流URL，并设置正确的图像保存路径。</p><h1 id="%E4%B8%89%E3%80%81%E5%B8%A7%E7%8E%87%E7%9A%84%E9%80%89%E6%8B%A9" tabindex="-1">三、帧率的选择</h1><h2 id="1%E3%80%81%E5%AE%9E%E6%97%B6%E6%80%A7%E8%A6%81%E6%B1%82" tabindex="-1">1、实时性要求</h2><ul><li>帧率定义了每秒显示的图像数量，通常以&quot;帧每秒&quot;（fps）表示。</li><li>实时或接近实时的应用，如视频监控，通常需要较高的帧率，建议使用30fps或更高。</li></ul><h2 id="2%E3%80%81%E8%80%83%E8%99%91%E8%B5%84%E6%BA%90%E9%99%90%E5%88%B6" tabindex="-1">2、考虑资源限制</h2><ul><li>高帧率需要更多的带宽和计算资源。</li><li>确保您的设备和网络能够支持所选的帧率，以避免性能问题。</li></ul><h2 id="3%E3%80%81%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF" tabindex="-1">3、应用场景</h2><ul><li>不同应用场景可能需要不同的帧率。</li><li>视频播放应用可以使用较低的帧率，而要求高质量图像的应用则可能需要更高的帧率。</li></ul><h2 id="4%E3%80%81%E5%AD%98%E5%82%A8%E9%9C%80%E6%B1%82" tabindex="-1">4、存储需求</h2><ul><li>高帧率会导致更多的视频数据，需要更多的存储空间。</li><li>考虑存储需求，特别是如果需要保存视频流供后续分析或回放。</li></ul><h1 id="%E5%9B%9B%E3%80%81%E5%88%86%E8%BE%A8%E7%8E%87%E7%9A%84%E9%80%89%E6%8B%A9" tabindex="-1">四、分辨率的选择</h1><h2 id="1%E3%80%81%E6%98%BE%E7%A4%BA%E8%AE%BE%E5%A4%87%E5%92%8C%E5%B1%8F%E5%B9%95%E5%A4%A7%E5%B0%8F" tabindex="-1">1、显示设备和屏幕大小</h2><ul><li>分辨率应适合最终显示图像的设备或屏幕大小。</li><li>高分辨率适合大型屏幕，低分辨率适合小型设备。</li></ul><h2 id="2%E3%80%81%E5%B8%A6%E5%AE%BD%E5%92%8C%E6%80%A7%E8%83%BD" tabindex="-1">2、带宽和性能</h2><ul><li>高分辨率图像通常需要更多带宽和计算资源。</li><li>在有限的带宽或性能条件下，选择适度的分辨率以确保流畅的抓取和显示。</li></ul><h2 id="3%E3%80%81%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF-1" tabindex="-1">3、应用场景</h2><ul><li>根据应用需求选择合适的分辨率。</li><li>720p（1280x720像素）或1080p（1920x1080像素）通常适合大多数实时监控应用。</li></ul><h2 id="4%E3%80%81%E5%AD%98%E5%82%A8%E9%9C%80%E6%B1%82-1" tabindex="-1">4、存储需求</h2><ul><li>高分辨率图像需要更多的存储空间。</li><li>考虑存储需求，特别是如果需要保存抓取的图像或视频流。</li></ul><h1 id="%E4%BA%94%E3%80%81%E8%AE%BE%E7%BD%AE%E5%B8%A7%E7%8E%87%E5%92%8C%E5%88%86%E8%BE%A8%E7%8E%87%E7%9A%84%E5%AE%9E%E9%99%85%E6%93%8D%E4%BD%9C" tabindex="-1">五、设置帧率和分辨率的实际操作</h1><p>要设置帧率和分辨率，您可以使用相应的方法来配置<code>FFmpegFrameGrabber</code>：</p><pre><code class="language-java">// 设置所需的帧率grabber.setFrameRate(desiredFrameRate);// 设置所需的分辨率grabber.setImageWidth(desiredWidth);grabber.setImageHeight(desiredHeight);</code></pre><p>确保在调用<code>grabber.start();</code>之前进行这些设置，以确保配置在抓取开始之前生效。</p><p>选择合适的帧率和分辨率是优化RTSP流视频抓取的关键步骤，可以提供良好的图像质量和实时性，同时考虑资源限制和存储需求。根据您的应用需求，选择最佳的参数设置，以获得最佳的用户体验。</p><h1 id="%E5%85%AD%E3%80%81%E5%AE%9E%E6%97%B6%E6%80%A7%E5%92%8C%E6%B5%81%E7%95%85%E6%80%A7%E7%9A%84%E6%9D%83%E8%A1%A1" tabindex="-1">六、实时性和流畅性的权衡</h1><p>在选择帧率和分辨率时，需要平衡实时性和流畅性。以下是一些有关权衡的考虑：</p><ul><li><p><strong>实时性</strong>：较高的帧率和分辨率可以提供更好的实时性，但可能需要更多的带宽和处理能力。在需要快速响应和高质量图像的应用中，实时性至关重要。</p></li><li><p><strong>流畅性</strong>：较高的帧率通常会导致更平滑的视频播放，但也需要更多的带宽。较低的帧率可能会导致视频看起来不够流畅，但在有限的带宽条件下可能是唯一可行的选择。</p></li><li><p><strong>网络条件</strong>：网络速度和稳定性对帧率和分辨率的选择至关重要。在不稳定的网络条件下，较低的帧率和分辨率可能更可取，以减少视频中断或缓冲。</p></li></ul><h1 id="%E4%B8%83%E3%80%81%E5%8A%A8%E6%80%81%E8%B0%83%E6%95%B4" tabindex="-1">七、动态调整</h1><p>有些应用可能需要根据情况动态调整帧率和分辨率。例如，当网络带宽下降时，可以降低帧率和分辨率以适应当前条件，从而保持视频的流畅性。</p><h1 id="%E7%BB%93%E8%AE%BA" tabindex="-1">结论</h1><p>选择合适的帧率和分辨率是优化RTSP流视频抓取的关键决策。根据应用的实时性要求、资源限制、显示设备、存储需求和网络条件，您可以调整这些参数以获得最佳的用户体验。实时性和流畅性之间的权衡是一个关键考虑因素，可以根据需要进行调整，以适应不同的应用场景。</p>]]>
                    </description>
                    <pubDate>Thu, 07 Sep 2023 15:36:00 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[沈阳盖章计划]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/shen-yang-gai-zhang-ji-hua</link>
                    <description>
                            <![CDATA[<h1 id="%E6%B2%88%E9%98%B3%E7%9B%96%E7%AB%A0%E8%AE%A1%E5%88%92" tabindex="-1">沈阳盖章计划</h1><h2 id="%E5%92%8C%E5%B9%B3%E5%8C%BA" tabindex="-1">和平区</h2><ul class="contains-task-list"><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 中共满洲省委门口服务站（2个）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 歌德书店（1个蓝色章）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳集邮门市部（38个章）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳东北大学成立100周年（东大风味食堂进去科学馆1923咖啡馆，定位到天猫超市，28个章）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 老北市6号门文创雪糕（8个章，12.8元雪糕）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"> 老北市1号门服务台（免费4个）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 老北市1号门服务台（消费39有34个章）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 老北市汉字主题书房（28个，38元）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 刘少奇旧居纪念馆(满洲省委旧址)</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 西西弗书店太原街万达F2（2个）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 西西弗书店万象城bl（2个）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 西西弗书店长白万象汇F2（2个）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 宋玉桂艺术馆（5个）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 雷锋主题邮局（4个）砂阳路邮局</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 茶话弄（沈阳太原街万达店）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 太原街中兴魔方小镇（2个）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 盛京邮局（2个）</p></li><li class="task-list-item"><p><input class="task-list-item-checkbox" disabled="" type="checkbox"> 阳光荟购物中心魔方小镇（2个）</p></li></ul><h2 id="%E9%93%81%E8%A5%BF%E5%8C%BA" tabindex="-1">铁西区</h2><ul class="contains-task-list"><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"> 西西弗书店万象汇F2(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" checked="" disabled="" type="checkbox"> 铁西1905文化创意园3楼小芝社(27个章，49)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 铁西1905文化创意园3楼微码客(2个章，任意消费，建议沈阳手绘地图29）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 铁西1905文化创意园一楼西门服务台边上有个店，进去礼貌询问店员即可免费盖</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 铁西1905文化创意园中间的市集一楼003摊位，蜜思花园，一家卖首饰的店，橡皮章，免费盖</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 铁西1905文化创意园楼一楼中间005摊位，CUTECATS。任意消费可盖</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 铁西1905文化创意园一楼中间008摊位，光之翼塔罗 免费盖</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 铁西1905文化创意园p6边上 @红雾动画工作室 关注小红书送扇子消费送贴纸</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 铁西1905文化创意园二楼右侧楼梯 旁，饰物所@daydream事务所</li></ul><h1 id="%E6%B2%88%E6%B2%B3%E5%8C%BA" tabindex="-1">沈河区</h1><ul class="contains-task-list"><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"> 小芝社故宫店(8个章+3个隐藏章，最低需消费30元)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 西西弗书店中街大悦城店F2(2个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳故宫.莊啡(18个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳故宫文创大政殿旁(12个章，通关文牒25)还有一个15元地图</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳故宫出口处旁(17个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳故宫戏台旁边长滩(6个章，17元明 信片)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 西西弗书店中街恒隆店F2(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"> 张氏帅府外面文创(120个章，49元盖章本)可拿文创本进帅府里面还有30枚印章(48门票)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 张氏帅府游客中心(3个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳金融博物馆（门票20，25元盖10个章，其中两个章在出口免费盖，买45还是48来着的本子可以多盖5枚金属圆章）</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳博物馆(12个章，39元盖章本)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 大悦城魔方小镇前台咖啡处北方书城(2个)免费(9月份才有)</li><li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"> 神马艺术商店中街HK01流行馆六楼(25个，任意消费)</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"> 沈阳中街HK01流行馆六楼&quot;是是是集”(2个)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳中街Hk01流行馆六楼&quot;Crunch冻春”(抵消20)(章数20+)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈阳迦大文创设计廊(7个)抵消30</li></ul><h1 id="%E6%B5%91%E5%8D%97%E5%8C%BA" tabindex="-1">浑南区</h1><ul class="contains-task-list"><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"> 西西弗书店全运路万达F1(2个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 辽宁科技馆(4个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"> 辽宁博物馆(19个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 东陵公园(1个章)</li></ul><h1 id="%E7%9A%87%E5%A7%91%E5%8C%BA" tabindex="-1">皇姑区</h1><ul class="contains-task-list"><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"> 新乐遗址(1个章)</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"> 辽宁古生物博物馆(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"> 审判日本战犯军事法庭(1个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 沈飞航空博览园(1个章)</li></ul><h1 id="%E5%A4%A7%E4%B8%9C%E5%8C%BA" tabindex="-1">大东区</h1><ul class="contains-task-list"><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"> 二战盟军战俘营陈列馆(1个章)</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 大悦城咖啡主题邮局(63个章)没有了</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"> 九一八邮局(15个章)</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"> 周恩来少年读书旧址(1个章)周六日对散客</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 呈展稻米节</li></ul><h1 id="%E6%B2%88%E5%8C%97%E5%8C%BA" tabindex="-1">沈北区</h1><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 锡伯族博物馆服务台和文创店(1+10)</li></ul>]]>
                    </description>
                    <pubDate>Thu, 31 Aug 2023 19:54:15 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[vue实现打印功能]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/vue-shi-xian-da-yin-gong-neng</link>
                    <description>
                            <![CDATA[<p>在Vue应用中调用打印机功能，可以使用<code>JavaScript</code>的<code>window.print()</code>方法。这个方法会打开打印对话框，然后让我们选择打印设置并打印文档，但是尼这种方法依赖于浏览器的打印功能。</p><p>以下是一个简单的示例，演示如何在Vue组件中调用打印功能：</p><ol><li>在Vue组件中，将需要打印的内容放入一个具有唯一ID的元素中。例如，你可以使用<code>&lt;div id=&quot;printable-content&quot;&gt;&lt;/div&gt;</code>来包裹打印内容。</li></ol><pre><code class="language-html">&lt;template&gt;  &lt;div&gt;    &lt;button @click=&quot;print&quot;&gt;打印&lt;/button&gt;    &lt;div id=&quot;printable-content&quot;&gt;      &lt;!-- 待打印的内容 --&gt;    &lt;/div&gt;  &lt;/div&gt;&lt;/template&gt;</code></pre><ol start="2"><li>在Vue组件的<code>methods</code>中定义<code>print</code>方法，该方法将获取打印内容并调用<code>window.print()</code>方法打开打印对话框。</li></ol><pre><code class="language-javascript">&lt;script&gt;export default {  methods: {    print() {      // 获取待打印的内容      let printableContent = document.getElementById(&#39;printable-content&#39;).innerHTML;            // 创建一个新的窗口并加载打印内容      let printWindow = window.open(&#39;&#39;, &#39;_blank&#39;);      printWindow.document.write(&#39;&lt;html&gt;&lt;head&gt;&lt;title&gt;打印内容&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&#39; + printableContent + &#39;&lt;/body&gt;&lt;/html&gt;&#39;);            // 执行打印操作      printWindow.document.close();            // 如果内容中有图片或其他需要一定时间加载的，请使用注释中的延时打印      // setTimeout(() =&gt; {      //   printWindow.print()      // }, 200)      printWindow.print();    }  }}&lt;/script&gt;</code></pre><ol start="3"><li>当点击&quot;打印&quot;按钮时，<code>print</code>方法会被调用，从而打开打印对话框。用户可以在对话框中选择打印设置并打印文档。</li></ol><p>最后，再次强调，这种方法依赖于浏览器的打印功能，因此它可能无法在所有打印机上正常工作。</p>]]>
                    </description>
                    <pubDate>Thu, 17 Aug 2023 10:07:49 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Java代码中对文件的操作]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/java-dai-ma-zhong-dui-wen-jian-de-cao-zuo</link>
                    <description>
                            <![CDATA[<h1 id="%E5%BC%95%E8%A8%80" tabindex="-1">引言</h1><p>这几天的项目涉及到了文件的操作，我这边做一下整理</p><h1 id="%E7%8E%AF%E5%A2%83%E8%AF%B4%E6%98%8E" tabindex="-1">环境说明</h1><p>jdk版本：1.8.0_311</p><h1 id="%E5%AF%B9%E6%96%87%E4%BB%B6%E7%9A%84%E6%93%8D%E4%BD%9C" tabindex="-1">对文件的操作</h1><h2 id="1%E3%80%81%E4%BF%9D%E5%AD%98%E6%96%87%E4%BB%B6" tabindex="-1">1、保存文件</h2><pre><code class="language-java">/** * 保存文件 * * @param file 文件 * @param path 文件保存目录 * @param name 保存后的文件名字 */public void saveFile(MultipartFile file, String path, String name) throws Exception {    if (file == null) {        throw new Exception(&quot;请上传有效文件!&quot;);    }    // 若目录不存在则创建目录    File folder = new File(path);    if (!folder.exists()) {        folder.mkdirs();    }    // 生成文件，folder为文件目录，newName为文件名    file.transferTo(new File(folder, name));}</code></pre><h2 id="2%E3%80%81%E5%88%A0%E9%99%A4%E6%96%87%E4%BB%B6" tabindex="-1">2、删除文件</h2><pre><code class="language-java">/** * 删除指定目录下的指定文件 * * @param path 文件路径(路径结尾不带“/”) * @param name 文件名称 */public void delFile(String path, String name) {    File file = new File(path + &quot;/&quot; + name);    file.delete();}</code></pre><h2 id="3%E3%80%81%E5%88%A0%E9%99%A4%E6%8C%87%E5%AE%9A%E7%9A%84%E7%A9%BA%E7%9B%AE" tabindex="-1">3、删除指定的空目</h2><pre><code class="language-java">/** * 删除指定的空目录，如果往上2层的目录也是空的，则一起删除 * * @param path 目录路径(路径结尾不带“/”) */public void delBlankDir(String path) {    for (int i = 0; i &lt; 3; i++) {        File dirFile = new File(path);        if (dirFile.length() == 0) {            dirFile.delete();            path = path.substring(0, path.lastIndexOf(&quot;/&quot;));        } else {            break;        }    }}</code></pre><h2 id="4%E3%80%81%E9%AA%8C%E8%AF%81%E6%96%87%E4%BB%B6%E6%98%AF%E5%90%A6%E6%98%AFmp3%E6%A0%BC%E5%BC%8F" tabindex="-1">4、验证文件是否是MP3格式</h2><pre><code class="language-java">/** * 验证是否是MP3格式的文件 * * @param multipartFile 验证的文件 * @return true：是MP3、false：不是MP3 */public boolean isMP3File(MultipartFile multipartFile) {    try {        byte[] headerBytes = new byte[4];        multipartFile.getInputStream().read(headerBytes);        if (headerBytes[0] == (byte) 0x49 &amp;&amp; headerBytes[1] == (byte) 0x44 &amp;&amp;                headerBytes[2] == (byte) 0x33) {            return true;        }    } catch (IOException e) {        e.printStackTrace();        return false;    }    return false;}</code></pre><h2 id="5%E3%80%81%E9%9F%B3%E9%A2%91%E6%A0%BC%E5%BC%8F%E8%BD%AC%E6%8D%A2" tabindex="-1">5、音频格式转换</h2><pre><code class="language-java">/** * 音频文件格式转换 * * @param fpath  需要转换的音频文件路径 * @param target 转换后的音频文件路径 */public void transferAudioPcm(String fpath, String target) {    List&lt;String&gt; commend = new ArrayList&lt;&gt;();    String path = &quot;&quot;;    if (SystemUtils.isLinux()) {        path = &quot;修改成Ffmpeg文件的路径&quot;;    } else if (SystemUtils.isWindows()) {        path = &quot;修改成Ffmpeg文件的路径&quot;;    }    commend.add(path);    commend.add(&quot;-y&quot;);    commend.add(&quot;-i&quot;);    commend.add(fpath);    commend.add(&quot;-f&quot;);    commend.add(&quot;s16le&quot;);    commend.add(&quot;-ar&quot;);    commend.add(&quot;4000&quot;);    commend.add(&quot;-ac&quot;);    commend.add(&quot;-1&quot;);    commend.add(target);    try {        ProcessBuilder builder = new ProcessBuilder();        builder.command(commend);        Process p = builder.start();        p.waitFor();        p.destroy();    } catch (Exception e) {        e.printStackTrace();    }}</code></pre><h2 id="6%E3%80%81%E6%94%B9%E5%8F%98linux%E7%B3%BB%E7%BB%9F%E4%B8%8B%E7%9A%84%E6%96%87%E4%BB%B6%E6%9D%83%E9%99%90" tabindex="-1">6、改变linux系统下的文件权限</h2><pre><code class="language-java">/** * 改变linux系统下的文件权限 * * @param mod  修改后的权限 * @param path 文件路径 */public void changePermission(String mod, String path) throws Exception {    // ProcessBuilder processBuilder = new ProcessBuilder(&quot;chmod&quot;, &quot;775&quot;, &quot;/data/a.txt&quot;);    ProcessBuilder processBuilder = new ProcessBuilder(&quot;chmod&quot;, mod, path);    Process process = processBuilder.start();    int exitCode = process.waitFor();    if (exitCode == 0) {        System.out.println(&quot;File permission changed successfully!&quot;);    } else {        System.out.println(&quot;Failed to change file permission.&quot;);    }}</code></pre><h2 id="7%E3%80%81%E6%9F%A5%E8%AF%A2%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%A3%81%E7%9B%98%E7%A9%BA%E9%97%B4" tabindex="-1">7、查询服务器磁盘空间</h2><pre><code class="language-java">/** * 查询服务器磁盘空间 * * @return map */public Map&lt;String, String&gt; getDiskInfo() {    // 总空间    long totalSpace = 0;    // 已用空间    long usableSpace = 0;    // 可用空间    long unallocatedSpace = 0;    for (FileStore fileStore : FileSystems.getDefault().getFileStores()) {        try {            totalSpace += fileStore.getTotalSpace();            usableSpace += fileStore.getUsableSpace();            unallocatedSpace += fileStore.getUnallocatedSpace();        } catch (IOException e) {            throw new RuntimeException(e);        }    }    DecimalFormat decimalFormat = new DecimalFormat(&quot;#.00&quot;);    Map&lt;String, String&gt; map = new HashMap&lt;&gt;(3);    map.put(&quot;totalSpace&quot;, decimalFormat.format(totalSpace / (1024.0 * 1024 * 1024)));    map.put(&quot;usableSpace&quot;, decimalFormat.format(usableSpace / (1024.0 * 1024 * 1024)));    map.put(&quot;unallocatedSpace&quot;, decimalFormat.format(unallocatedSpace / (1024.0 * 1024 * 1024)));    return map;}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 15 Aug 2023 17:22:15 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[群晖nas为PHP配置Redis扩展]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/qun-hui-nas-wei-php-pei-zhi-redis-kuo-zhan</link>
                    <description>
                            <![CDATA[<p>首先，介绍下我的环境</p><ul><li><p>机器：群晖920+</p></li><li><p>PHP版本：7.4（套件安装的）</p></li><li><p>系统：群晖7.2</p></li></ul><p>接下来，进入正题</p><p>首先要使用ssh进入到群晖，账户要切换到root用户</p><p>接下来，看下目前PHP7.4有哪些扩展，根据你安装位置的硬盘不同，<code>volume1</code>可能有所区别，命令如下：</p><pre><code class="language-shell">ll /volume1/@appstore/PHP7.4/usr/local/lib/php74/modules</code></pre><p><img src="https://img.huangge1199.cn/blog/nasPhpRedis/2023-07-27-10-26-27-image.png" alt="" /></p><p>从上图中，我发现套件版的PHP7.4默认已经有了Redis扩展，接下来，再看看配置文件中是否配置了Redis，当然我这边是没有配置</p><p>打开配置文件<code>php-fpm.ini</code>，我这边喜欢用<code>vi</code>命令，当然也可以使用<code>vim</code>，具体用哪一个看你系统支持已经个人喜好了，下面的<code>volume1</code>一样有区别的自行修改</p><pre><code class="language-shell">vi /volume1/@appstore/PHP7.4/misc/php-fpm.ini</code></pre><p>将下面的代码放到配置文件<code>php-fpm.ini</code>末尾，然后保存退出</p><pre><code class="language-shell">[Redis]extension_dir = &quot;/volume1/@appstore/PHP7.4/usr/local/lib/php74/modules/&quot;extension = redis.so</code></pre><p><img src="https://img.huangge1199.cn/blog/nasPhpRedis/2023-07-27-10-46-52-image.png" alt="" /></p><p>扩展有了，配置文件也加上了，最后就是重启PHP7.4了，命令如下：</p><pre><code class="language-shell">synopkg restart PHP7.4</code></pre><p><img src="https://img.huangge1199.cn/blog/nasPhpRedis/2023-07-27-10-48-36-image.png" alt="" /></p><p>看到重启成功了，至此，完成收工了</p>]]>
                    </description>
                    <pubDate>Thu, 27 Jul 2023 12:35:23 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin中steam的配置]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin中steam的配置</link>
                    <description>
                            <![CDATA[<h1 id="deepin中steam的配置">deepin中steam的配置</h1><p>本人的deepin系统已经更新到20.9，文章仅供参考，可能会与你的情况有所出入</p><h1 id="下载安装">下载安装</h1><p>在deepin的应用商店中下载安装steam</p><p><img src="https://img.huangge1199.cn/blog/steamByDeepin/image-20230624125037-advf2v5.png" alt="image" /></p><h1 id="中文设置">中文设置</h1><p>我这边安装完默认是英文界面，可以依次点击左上角是steam--》settings</p><p><img src="https://img.huangge1199.cn/blog/steamByDeepin/image-20230624125532-2i70ung.png" alt="image" /></p><p>在弹出的页面中点击左侧Interface，右侧按照红框内容选择简体中文</p><p><img src="https://img.huangge1199.cn/blog/steamByDeepin/image-20230624125628-haoo17i.png" alt="image" /></p><p>然后在点击重启按钮即可</p><p><img src="https://img.huangge1199.cn/blog/steamByDeepin/image-20230624125826-24e5l7j.png" alt="image" /></p><p>重启后界面已经是中文的啦</p><p><img src="https://img.huangge1199.cn/blog/steamByDeepin/image-20230624130025-64dih9w.png" alt="image" /></p><h1 id="兼容大多数的游戏">兼容大多数的游戏</h1><p>依次点击左上角的steam--&gt;设置(英文版setting)--&gt;弹出页面的兼容性，将红框的内容都设置完在一起重启，Proton那个下拉框选择最新的就好，我现在是8.0-2最新，也许你的并不一定是。</p><p><img src="https://img.huangge1199.cn/blog/steamByDeepin/image-20230624130924-772dq4m.png" alt="image" /></p><p>重启后，你就发现之前不能玩的大多数游戏都可以玩了</p>]]>
                    </description>
                    <pubDate>Sat, 24 Jun 2023 13:34:05 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[浏览器不走代理Proxifier问题解决]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/liu-lan-qi-bu-zou-dai-li-proxifier-wen-ti-jie-jue</link>
                    <description>
                            <![CDATA[<p>环境需要用到代理软件Proxifier，但配好后chrome浏览器访问对应代理的应用却不行，之前还明明可以的，今天就突然不行了，于是乎，我去排查了原因，本篇文章就是我排查的记录吧，最后问题是解决了。每个人的情况不同，可能我的办法不一定适用于你的，因此，本篇文章仅做参考。</p><h1 id="%E7%A1%AE%E8%AE%A4proxifier%E8%AE%BE%E7%BD%AE" tabindex="-1">确认Proxifier设置</h1><ol><li>打开Proxifier，选择菜单栏的“Profile” - “Proxification Rules”。</li><li>在“Proxification Rules”中，确认走代理的应用程序包含浏览器。如果不包含，可单击右键选择“Edit Selected Rule”，在“Edit Rule”中，设置“Any”为“Applications”后点击OK。</li></ol><h1 id="%E7%A1%AE%E8%AE%A4%E7%B3%BB%E7%BB%9F%E4%BB%A3%E7%90%86%E8%AE%BE%E7%BD%AE" tabindex="-1">确认系统代理设置</h1><p>首先，说明一下，本人是Win11的系统，可能会与你的有出入，下面是详细步骤：</p><ol><li>点击电脑的开始菜单，打开设置。</li><li>点击左侧的“网络和Internet”，再点击右侧的代理。</li></ol><p><img src="https://img.huangge1199.cn/blog/proxifier-proxy-fix/2023-06-20-15-45-42-image.png" alt="" /></p><ol start="3"><li>确认页面中红框的内容全是关闭状态，如果不是，改为关闭状态</li></ol><p><img src="https://img.huangge1199.cn/blog/proxifier-proxy-fix/2023-06-20-15-46-58-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Tue, 20 Jun 2023 14:04:18 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[使用xlsxwriter和openplxl库操作Excel文件]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/shi-yong-xlsxwriter-he-openplxl-ku-cao-zuo-excel-wen-jian</link>
                    <description>
                            <![CDATA[<p>Excel文件是一种广泛使用的电子表格格式，用于存储和处理各种数据。在Python中，有多个库可以用于处理Excel文件，其中包括xlsxwriter和openplxl两个库。本文将介绍这两个库的使用方法以及如何使用它们来操作Excel文件。</p><h1 id="1%E3%80%81xlsxwriter%E7%94%9F%E6%88%90excel%E6%96%87%E4%BB%B6" tabindex="-1">1、xlsxwriter生成Excel文件</h1><p>xlsxwriter是一个用于生成Excel文件的Python库，支持多种格式的Excel文件(如.xlsx、.xlsm、.xltx、.xltm等),并且支持自定义样式和格式。下面将以一个简单的示例，来逐步介绍如何使用xlsxwriter库创建一个Excel文件并写入数据</p><h2 id="1.1%E3%80%81%E5%AF%BC%E5%85%A5%E5%BA%93%E5%B9%B6%E5%88%9B%E5%BB%BAexcel%E6%96%87%E4%BB%B6" tabindex="-1">1.1、导入库并创建Excel文件</h2><p>Excel文件名为：xlsxwriter插入数据和折线图.xlsx</p><pre><code class="language-python">import xlsxwriter, randomwb = xlsxwriter.Workbook(&#39;xlsxwriter插入数据和折线图.xlsx&#39;)</code></pre><h2 id="1.2%E3%80%81%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AAsheet%E9%A1%B5" tabindex="-1">1.2、创建一个sheet页</h2><p>sheet标签页名字为：sheet1</p><pre><code class="language-python">worksheet1 = wb.add_worksheet(&#39;sheet1&#39;)</code></pre><h2 id="1.3%E3%80%81%E6%8C%89%E8%A1%8C%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE" tabindex="-1">1.3、按行写入数据</h2><p>这里以写入Excel的头部数据为例：</p><pre><code class="language-python">headings = [&#39;日期&#39;,&#39;数据1&#39;,&#39;数据2&#39;]worksheet1.write_row(&#39;A1&#39;,headings)</code></pre><h2 id="1.4%E3%80%81%E6%8C%89%E5%88%97%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE" tabindex="-1">1.4、按列写入数据</h2><p>有了头部数据后，该写入下面的实际数据了</p><pre><code class="language-python"># 创造数据data = [    [&#39;2019-1&#39;,&#39;2019-2&#39;,&#39;2019-3&#39;,&#39;2019-4&#39;,&#39;2019-5&#39;,&#39;2019-6&#39;,&#39;2019-7&#39;,&#39;2019-8&#39;,&#39;2019-9&#39;,&#39;2019-10&#39;,&#39;2019-11&#39;,&#39;2019-12&#39;,],    [random.randint(1,100) for i in range(12)],    [random.randint(1,100) for i in range(12)],] # 按列写入数据worksheet1.write_column(&#39;A2&#39;,data[0])worksheet1.write_column(&#39;B2&#39;,data[1])worksheet1.write_column(&#39;C2&#39;,data[2])</code></pre><h2 id="1.5%E3%80%81%E6%96%B0%E5%BB%BA%E5%9B%BE%E8%A1%A8%E5%AF%B9%E8%B1%A1" tabindex="-1">1.5、新建图表对象</h2><p>折线图表的定义：</p><pre><code class="language-python">chart_col = wb.add_chart({&#39;type&#39;:&#39;line&#39;})</code></pre><h2 id="1.6%E3%80%81%E5%9B%BE%E8%A1%A8%E6%95%B0%E6%8D%AE%E9%85%8D%E7%BD%AE" tabindex="-1">1.6、图表数据配置</h2><p>这里的数据有两条，一个是数据1，一个是数据2，所以图表添加数据的代码如下：</p><pre><code class="language-python">chart_col.add_series(    {        &#39;name&#39;:&#39;=sheet1!$B$1&#39;,        &#39;categoies&#39;:&#39;=sheet1!$A$2:$A$7&#39;,        &#39;values&#39;:   &#39;=sheet1!$B$2:$B$7&#39;,        &#39;line&#39;: {&#39;color&#39;: &#39;blue&#39;},    })chart_col.add_series(    {        &#39;name&#39;:&#39;=sheet1!$C$1&#39;,        &#39;categories&#39;:&#39;=sheet1!$A$2:$A$7&#39;,        &#39;values&#39;:   &#39;=sheet1!$C$2:$C$7&#39;,        &#39;line&#39;: {&#39;color&#39;: &#39;green&#39;},    })</code></pre><p>有两条数据，所以添加了两次。</p><p>数据有四项，数据名、具体值对应的横坐标categories、具体值对应的纵坐标values、折线颜色，其中取值方式，直接是使用sheet的坐标形式，例如name是B1和B2，categories都是A2-A7，值分别是B2-B7和C2-C7。</p><h2 id="1.7%E3%80%81%E5%AE%8C%E6%88%90%E5%9B%BE%E8%A1%A8" tabindex="-1">1.7、完成图表</h2><p>数据添加之后，在设置下坐标的相关信息，就是标题、x轴、y轴的名字，以及图表位置和大小，代码如下：</p><pre><code class="language-python">chart_col.set_title({&#39;name&#39;:&#39;虚假数据折线图&#39;})chart_col.set_x_axis({&#39;name&#39;:&quot;横坐标&quot;})chart_col.set_y_axis({&#39;name&#39;:&#39;纵坐标&#39;})worksheet1.insert_chart(&#39;D2&#39;,chart_col,{&#39;x_offset&#39;:25,&#39;y_offset&#39;:10})wb.close()</code></pre><p>图表的位置和大小，是根据左上角的起始表格和x和y的偏移计算的。</p><p>代码中是从D2做左上角起始，然后x和y分别便宜25和10个单位，得到了图片的最终大小。最后关闭wb。</p><p><img src="https://img.huangge1199.cn/blog/pyHighExcel/2023-05-31-13-51-38-image.png" alt="" /></p><h1 id="2%E3%80%81openpyxl%E8%BF%BD%E5%8A%A0excel%E6%95%B0%E6%8D%AE" tabindex="-1">2、openpyxl追加Excel数据</h1><p>openplxl是一个用于读取现有的Excel文件的Python库，支持多种格式的Excel文件(如.xlsx、.xlsm、.xltx、.xltm等),并且支持读取单元格的数据。</p><h2 id="2.1%E3%80%81%E6%89%93%E5%BC%80%E6%96%87%E4%BB%B6" tabindex="-1">2.1、打开文件</h2><pre><code class="language-python">import openpyxlfilename = &#39;xlsxwriter插入数据和折线图.xlsx&#39;wb = openpyxl.load_workbook(filename)</code></pre><h2 id="2.2%E3%80%81%E6%8B%B7%E8%B4%9Dsheet" tabindex="-1">2.2、拷贝sheet</h2><pre><code class="language-python">sheet1 = wb[&#39;sheet1&#39;]# 拷贝sheet1sheet2 = wb.copy_worksheet(sheet1)# 设置拷贝后的名称为sheet2sheet2.title = &quot;sheet2&quot;</code></pre><h2 id="2.3%E3%80%81%E8%BF%BD%E5%8A%A0%E6%95%B0%E6%8D%AE%E5%86%85%E5%AE%B9" tabindex="-1">2.3、追加数据内容</h2><p>在sheet2中，数据1和数据2追加一年的数据，代码如下：</p><pre><code class="language-python"># 读取最后一行rows = sheet2.max_row# 取出时间的字符串prev_date_str = sheet2.cell(row=rows,column=1).value# 时间字符串转时间对象prev_date = datetime.datetime.strptime(prev_date_str, &quot;%Y-%m&quot;)for i in range(1,13):    # 月份的计算，每次增加一个月，就得到了第二年的12个月    tmp_date = prev_date + relativedelta(months=i)    tmp_num1 = random.randint(1,100)    tmp_num2 = random.randint(1,100)    sheet2.append([tmp_date.strftime(&quot;%Y-%m&quot;), tmp_num1, tmp_num2])</code></pre><p>实现思路：</p><ul><li>读取出最后一行</li><li>取出时间字符串，然后转换成时间对象</li><li>一年12个月，循环12次，每次增加一个月，数值可以随机的生成，用random即可</li><li>数据的追加，是按每行，所以将【时间， 数据1， 数据2】通过append直接追加到数据最后即可</li></ul><h2 id="2.4%E3%80%81%E4%BD%BF%E7%94%A8openpyxl%E7%94%BB%E5%9B%BE%E8%A1%A8" tabindex="-1">2.4、使用openpyxl画图表</h2><p>在sheet2中对全部数据画折线图</p><pre><code class="language-python">from openpyxl.chart import Series,LineChart, Reference# 图表对象chart = LineChart()rows = sheet2.max_row# 创建series对象data1 = Reference(sheet2, min_col=2, min_row=1, max_col=2, max_row=rows) #涉及数据title1 = sheet2.cell(row=1,column=2).valueseriesObj1 = Series(data1, title=title1)# 创建series对象data2 = Reference(sheet2, min_col=3, min_row=1, max_col=3, max_row=rows) #涉及数据title2 = sheet2.cell(row=1,column=3).valueseriesObj2 = Series(data2, title=title2)# 添加到chart中chart.append(seriesObj1)chart.append(seriesObj2)# 将图表添加到 sheet中sheet2.add_chart(chart, &quot;E3&quot;)# 保存Excelwb.save(&#39;poenpyxl插入数据和折线图[copy xlsxwriter].xlsx&#39;)</code></pre><p>导入所需的画图工具，图表初始化，然后生成数据对象：</p><ul><li>data1的生成，因为索引从1开始，所以标题是第一行第二列，数据是第二行第二列，一直到最后一行第二列</li><li>data2的生成，因为索引从1开始，所以标题是第一行第三列，数据是第二行第三列，一直到第二行最后三列</li><li>将两个数据都放到图表内</li><li>然后图表的开始位置，设置成E3，数据在ABC，E是空的，3距离顶部有两格的位置</li></ul><p>最后文件保存，大功告成。</p><p><img src="https://img.huangge1199.cn/blog/pyHighExcel/2023-05-31-14-04-47-image.png" alt="" /></p><p><img src="https://img.huangge1199.cn/blog/pyHighExcel/2023-05-31-14-05-20-image.png" alt="" /></p><h1 id="3%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">3、总结</h1><p>本文介绍了如何使用xlsxwriter和openplxl两个库来操作Excel文件。xlsxwriter库可以用于创建新的Excel文件并写入数据，而openplxl库则可以用于读取现有的Excel文件并读取单元格的数据。这些库都是Python中处理Excel文件的好工具，可以帮助我们更加高效地处理各种数据。</p>]]>
                    </description>
                    <pubDate>Wed, 31 May 2023 14:09:54 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[如何使用Python操作Excel文件？看这篇博客就够了！]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/ru-he-shi-yong-python-cao-zuo-excel-wen-jian--kan-zhe-pian-bo-ke-jiu-gou-le-</link>
                    <description>
                            <![CDATA[<h1 id="%E5%89%8D%E8%A8%80" tabindex="-1">前言</h1><p>如何使用Python操作Excel文件？看这篇博客就够了！</p><p>在工作中，我们经常需要处理和分析数据。而Excel作为一种广泛使用的数据分析工具，被很多人所熟知。但是，对于一些非技术背景的用户来说，如何操作Excel却可能有些困难。这时候，Python就成为了一种非常有用的工具。</p><p>本文将介绍如何使用Python对Excel文件进行读写操作。首先，我们将介绍Python中可以使用的第三方库<code>xlrd</code>、<code>xlwt</code>和<code>xlutils</code>,并通过示例来展示“如何使用<code>xlwt</code>库来将数据写入到Excel文件中”、“如何使用<code>xlrd</code>库来读取一个Excel文件的数据”和“如何使用三个库的配合来进行一边读取一边写入的操作”。</p><p>通过本文的介绍，你将会了解到：</p><ul><li>如何获取单元格的值；</li><li>如何遍历整个工作表；</li><li>如何创建新的工作表和单元格，并将数据写入到单元格中；</li><li>如何使用<code>save()</code>方法保存Excel文件到磁盘上。</li></ul><p>如果你想学习如何使用Python操作Excel文件，那么这篇文章就是为你准备的。希望它能帮助你更好地理解和应用这个工具。</p><h1 id="1%E3%80%81%E5%86%99%E5%85%A5excel%E6%96%87%E4%BB%B6" tabindex="-1">1、写入Excel文件</h1><p>首先来学习下，随机生成数据，写入一个Excel文件并保存，所使用到的库，是xlwt，安装命令<code>pip install xlwt</code> ，安装简单方便，无依赖，很快。</p><h2 id="1.1%E3%80%81%E6%96%B0%E5%BB%BA%E4%B8%80%E4%B8%AAworkbook%E5%AF%B9%E8%B1%A1" tabindex="-1">1.1、新建一个WorkBook对象</h2><pre><code class="language-python">import xlwtwb = xlwt.Workbook()</code></pre><h2 id="1.2%E3%80%81%E6%96%B0%E5%BB%BAsheet" tabindex="-1">1.2、新建sheet</h2><pre><code class="language-python">sheet = wb.add_sheet(&#39;第一个sheet&#39;)</code></pre><h2 id="1.3%E3%80%81%E5%86%99%E6%95%B0%E6%8D%AE" tabindex="-1">1.3、写数据</h2><pre><code class="language-python">head_data = [&#39;姓名&#39;,&#39;地址&#39;,&#39;手机号&#39;,&#39;城市&#39;]for head in head_data:    sheet.write(0,head_data.index(head),head)</code></pre><p><code>write</code>函数写入，分别是x行 x列 数据，头部数据永远是第一行，所以第0行。数据的列，则是当前数据所在列表的索引，直接使用index函数即可。</p><h2 id="1.4%E3%80%81%E5%88%9B%E5%BB%BA%E8%99%9A%E5%81%87%E6%95%B0%E6%8D%AE" tabindex="-1">1.4、创建虚假数据</h2><p>有了头部数据，现在就开始写入内容了，分别是随机姓名 随机地址 随机号码 随机城市，数据的来源都是faker库，一个专门创建虚假数据用来测试的库，安装命令：<code>pip install faker</code>。</p><p>因为头部信息已经写好，所以接下来是从第1行开始写数据，每行四个数据，准备写99个用户数据，所以用循环，循环99次，代码如下：</p><pre><code class="language-python">import fakerfake = faker.Faker()for i in range(1,100):    sheet.write(i,0,fake.first_name() + &#39; &#39; + fake.last_name())    sheet.write(i,1,fake.address())    sheet.write(i,2,fake.phone_number())    sheet.write(i,3,fake.city())</code></pre><h2 id="1.5%E3%80%81%E4%BF%9D%E5%AD%98%E6%88%90xls%E6%96%87%E4%BB%B6" tabindex="-1">1.5、保存成xls文件</h2><pre><code class="language-python">wb.save(&#39;虚假用户数据.xls&#39;)</code></pre><p>然后找到文件，使用office或者wps打开这个xls文件：</p><p><img src="https://img.huangge1199.cn/blog/pyExcel/2023-05-30-14-15-53-image.png" alt="" /></p><h1 id="2%E3%80%81%E8%AF%BB%E5%8F%96excel%E6%96%87%E4%BB%B6" tabindex="-1">2、读取Excel文件</h1><p>写文件已经搞定，接下来就要学习下Excel的读操作，读取Excel的库是xlrd，对应read；xlrd的安装命令：<code>pip install xlrd</code></p><h2 id="2.1%E3%80%81%E6%89%93%E5%BC%80excel%E6%96%87%E4%BB%B6" tabindex="-1">2.1、打开Excel文件</h2><pre><code class="language-python">import xlrdwb = xlrd.open_workbook(&#39;虚假用户数据.xls&#39;)</code></pre><h2 id="2.2%E3%80%81%E8%AF%BB%E5%8F%96excel%E6%95%B0%E6%8D%AE" tabindex="-1">2.2、读取Excel数据</h2><pre><code class="language-python"># 获取文件中全部的sheet，返回结构是list。sheets = wb.sheets()# 通过索引顺序获取。sheet = sheets[0]# 直接通过索引顺序获取。sheet = wb.sheet_by_index(0)# 通过名称获取。sheet = wb.sheet_by_name(&#39;第一个sheet&#39;)</code></pre><h2 id="2.3%E3%80%81%E6%89%93%E5%8D%B0%E6%95%B0%E6%8D%AE" tabindex="-1">2.3、打印数据</h2><pre><code class="language-python"># 获取行数rows = sheet.nrows# 获取列数cols = sheet.ncolsfor row in range(rows):    for col in range(cols):        print(sheet.cell(row,col).value,end=&#39; , &#39;)    print(&#39;\n&#39;)</code></pre><p>打印效果（只截取部分）：</p><p><img src="https://img.huangge1199.cn/blog/pyExcel/2023-05-30-14-14-29-image.png" alt="" /></p><h1 id="3%E3%80%81%E5%9C%A8%E7%8E%B0%E6%9C%89%E7%9A%84excel%E6%96%87%E4%BB%B6%E4%B8%AD%E8%BF%BD%E5%8A%A0%E5%86%85%E5%AE%B9" tabindex="-1">3、在现有的Excel文件中追加内容</h1><p>需求：往“虚假用户数据.xls”里面，追加额外的50条用户数据，就是标题+数据，达到150条。</p><h2 id="3.1%E3%80%81%E5%AF%BC%E5%85%A5%E5%BA%93" tabindex="-1">3.1、导入库</h2><pre><code class="language-python">import xlrdfrom xlutils.copy import copy</code></pre><h2 id="3.2%E3%80%81%E4%BD%BF%E7%94%A8xlrd%E6%89%93%E5%BC%80%E6%96%87%E4%BB%B6%EF%BC%8C%E7%84%B6%E5%90%8Exlutils%E8%B5%8B%E5%80%BC%E6%89%93%E5%BC%80%E5%90%8E%E7%9A%84workbook" tabindex="-1">3.2、使用xlrd打开文件，然后xlutils赋值打开后的workbook</h2><pre><code class="language-python">wb = xlrd.open_workbook(&#39;虚假用户数据.xls&#39;,formatting_info=True)xwb = copy(wb)</code></pre><h2 id="3.3%E3%80%81%E6%9C%89%E4%BA%86workbook%E4%B9%8B%E5%90%8E%EF%BC%8C%E5%B0%B1%E5%BC%80%E5%A7%8B%E6%8C%87%E5%AE%9Asheet%EF%BC%8C%E5%B9%B6%E8%8E%B7%E5%8F%96%E8%BF%99%E4%B8%AAsheet%E7%9A%84%E6%80%BB%E8%A1%8C%E6%95%B0" tabindex="-1">3.3、有了workbook之后，就开始指定sheet，并获取这个sheet的总行数</h2><pre><code class="language-python">sheet = xwb.get_sheet(&#39;第一个sheet&#39;)rows = sheet.get_rows()</code></pre><p>指定名称为“第一个sheet”的sheet，然后获取全部的行</p><h2 id="3.4%E3%80%81%E6%9C%89%E4%BA%86%E5%85%B7%E4%BD%93%E7%9A%84%E8%A1%8C%E6%95%B0%EF%BC%8C%E7%84%B6%E5%90%8E%E4%BF%9D%E8%AF%81%E5%8E%9F%E6%9C%89%E6%95%B0%E6%8D%AE%E4%B8%8D%E5%8F%98%E5%8A%A8%E7%9A%84%E6%83%85%E5%86%B5%E4%B8%8B%EF%BC%8C%E4%BB%8E%E7%AC%AC101%E8%A1%8C%E5%86%99%E6%95%B0%E6%8D%AE" tabindex="-1">3.4、有了具体的行数，然后保证原有数据不变动的情况下，从第101行写数据</h2><pre><code class="language-python">import fakerfake = faker.Faker()for i in range(len(rows),150):    sheet.write(i,0,fake.first_name() + &#39; &#39; + fake.last_name())    sheet.write(i,1,fake.address())    sheet.write(i,2,fake.phone_number())    sheet.write(i,3,fake.city())</code></pre><p>range函数，从len(rows)开始，到150-1结束，共50条。 faker库是制造虚假数据的，这个在前面写数据有用过，循环写入了50条。</p><h2 id="3.5%E3%80%81%E6%9C%80%E5%90%8E%E4%BF%9D%E5%AD%98%E5%B0%B1%E5%8F%AF%E4%BB%A5%E4%BA%86" tabindex="-1">3.5、最后保存就可以了</h2><pre><code class="language-python">xwb.save(&#39;虚假用户数据.xls&#39;)</code></pre><p>使用xwb，也就是操作之后的workbook对象，直接保存原来的文件名就可以了。</p><h1 id="4%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">4、总结</h1><p>本文介绍了Python中常用的三个库：xlrd、xlwt和xlutils,分别用于读取Excel文件、写入Excel文件和处理Excel文件。这些库都有各自的优点和缺点，在实际使用时需要根据具体需求进行选择。</p><p>同时，本文还提供了一些示例代码来演示如何使用这些库。通过这些示例代码，读者可以更好地了解这些库的使用方法和操作步骤。</p><p>最后，提醒读者注意在使用这些库时要仔细阅读其文档和API,以避免出现不必要的错误。</p><h1 id="5%E3%80%81%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" tabindex="-1">5、参考资料</h1><ul><li><a href="https://www.w3cschool.cn/minicourse/play/pythonwork" target="_blank">W3Cschool微课（Python 自动化办公课程）</a></li><li><a href="https://docs.python.org/zh-cn/3/library/xlrd.html" target="_blank">Python官方文档–xlrd</a></li><li><a href="https://docs.python.org/zh-cn/3/library/xlwt.html" target="_blank">Python官方文档–xlwt</a></li><li><a href="https://docs.python.org/zh-cn/3/library/xlutils.html" target="_blank">Python官方文档–xlutils</a></li></ul>]]>
                    </description>
                    <pubDate>Tue, 30 May 2023 14:11:38 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[element-ui 级联选择器el-cascader动态加载]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/element-ui-ji-lian-xuan-ze-qi-el-cascader-dong-tai-jia-zai</link>
                    <description>
                            <![CDATA[<p>看过了<a href="https://element.eleme.cn/#/zh-CN/component/cascader" target="_blank">el-cascade官方说明</a>后，发现并不适用于我，也可以说这个例子不是太适合想要直接复制粘贴的我，于是乎，我将其改写成了可以直接复制粘贴的例子，具体代码如下：</p><pre><code class="language-html">&lt;template&gt;  &lt;el-cascader    :props=&quot;cascaderProps&quot;    v-model=&quot;dataid&quot;    clearable&gt;  &lt;/el-cascader&gt;&lt;/template&gt;&lt;script&gt;export default {  name: &quot;cascader&quot;,  data() {    return {      dataid: [],      cascaderProps: {        lazy: true,        lazyLoad: this.lazyLoad      },    }  },  methods: {    lazyLoad(node, resolve) {      // 获取当前是第几层      let level = node.level      let nodes = []      // 我这边是两层，因此可根据是否有数据来做判断条件      // 可改为根据不同层级判断      if (!node.data) {        nodes = [          {            value: &#39;beijing&#39;,            label: &#39;北京&#39;,            leaf: level &gt;= 1          },          {            value: &#39;shanghai&#39;,            label: &#39;上海&#39;,            leaf: level &gt;= 1          },        ]      } else {        if (node.value === &quot;beijing&quot;) {          nodes = [            {              value: &#39;chaoyang&#39;,              label: &#39;朝阳区&#39;,              leaf: level &gt;= 1            },            {              value: &#39;haidian&#39;,              label: &#39;海淀区&#39;,              leaf: level &gt;= 1            },          ]        } else {          nodes = [            {              value: &#39;pudong&#39;,              label: &#39;浦东新区&#39;,              leaf: level &gt;= 1            },            {              value: &#39;hongkou&#39;,              label: &#39;虹口区&#39;,              leaf: level &gt;= 1            },          ]        }      }      // 通过调用resolve将子节点数据返回，通知组件数据加载完成      resolve(nodes)    }  }}&lt;/script&gt;</code></pre>]]>
                    </description>
                    <pubDate>Fri, 19 May 2023 15:58:26 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[从前端到后端：如何在 URL 参数中传递 JSON 数据]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/从前端到后端如何在url参数中传递json数据</link>
                    <description>
                            <![CDATA[<h1 id="%E5%BC%95%E8%A8%80" tabindex="-1">引言</h1><p>在 Web 开发中，我们经常需要将数据作为 URL 参数进行传递。当我们需要传递复杂的数据结构时，如何在前端将其转换为字符串，并在后端正确地解析它呢？本文将介绍如何在前端将 JSON 数据进行 URL 编码，并在后端将其解析为相应的数据类型，同时提供 Java 语言的示例代码。</p><h1 id="%E5%9C%A8%E5%89%8D%E7%AB%AF%E4%BD%BF%E7%94%A8-url-%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92-json-%E6%95%B0%E6%8D%AE" tabindex="-1">在前端使用 URL 参数传递 JSON 数据</h1><p>有时候我们需要在前端将 JSON 数据传递给后端，例如通过 AJAX 请求或者页面跳转。URL 参数是一种常见的传递数据的方式，但是由于 URL 参数只支持字符串类型的数据，而 JSON 数据是一种复杂的数据类型，因此需要进行编码和解码操作。</p><p>在 JavaScript 中，我们可以使用 <code>JSON.stringify()</code> 方法将 JSON 对象转换为字符串，然后使用 <code>encodeURIComponent()</code> 方法对字符串进行 URL 编码。以下是一个将 JSON 数据作为 URL 参数发送 AJAX 请求的示例：</p><pre><code class="language-javascript">const data = { name: &#39;John&#39;, age: 30 };const encodedData = encodeURIComponent(JSON.stringify(data));fetch(&#96;/api/user?data=${encodedData}&#96;)  .then(response =&gt; response.json())  .then(data =&gt; console.log(data))  .catch(error =&gt; console.error(error));</code></pre><p>在上面的示例中，我们首先创建了一个包含两个属性的 JSON 对象 <code>data</code>，然后将其转换为字符串并进行 URL 编码。然后我们使用 <code>fetch()</code> 方法发送一个带有 <code>data</code> 参数的 GET 请求，并在响应中使用 <code>json()</code> 方法将响应体解析为 JSON 对象。</p><h1 id="%E5%9C%A8%E5%90%8E%E7%AB%AF%E8%A7%A3%E6%9E%90-url-%E5%8F%82%E6%95%B0" tabindex="-1">在后端解析 URL 参数</h1><p>在后端中，我们需要解析从前端发送的包含 JSON 数据的 URL 参数。不同的后端语言和框架可能有不同的解析方式，这里以 Node.js 和 Java 为例，介绍如何解析 URL 参数。</p><h2 id="%E5%9C%A8-node.js-%E4%B8%AD%E8%A7%A3%E6%9E%90-url-%E5%8F%82%E6%95%B0" tabindex="-1">在 Node.js 中解析 URL 参数</h2><p>在 Node.js 中，我们可以使用内置的 <code>url</code> 模块来解析 URL 参数，使用 <code>querystring</code> 模块来解析查询字符串参数。以下是一个使用 Node.js 解析 URL 参数的示例：</p><pre><code class="language-javascript">const http = require(&#39;http&#39;);const url = require(&#39;url&#39;);const querystring = require(&#39;querystring&#39;);const server = http.createServer((req, res) =&gt; {  const parsedUrl = url.parse(req.url);  const parsedQuery = querystring.parse(parsedUrl.query);  // 解析包含在 &#39;data&#39; 参数中的 JSON 字符串  const rawData = parsedQuery.data;  const myObject = JSON.parse(decodeURIComponent(rawData));  // 执行其他操作...  res.writeHead(200, { &#39;Content-Type&#39;: &#39;text/plain&#39; });  res.end(&#39;Hello World!&#39;);});server.listen(3000, () =&gt; {  console.log(&#39;Server running on port 3000&#39;);});</code></pre><p>在上面的示例中，我们首先使用 <code>url.parse()</code> 方法将请求 URL 解析为 URL 对象，然后使用 <code>querystring.parse()</code> 方法将查询字符串参数解析为对象。然后，我们从 <code>data</code> 参数中获取包含 JSON 字符串的原始数据，使用 <code>decodeURIComponent()</code> 解码该字符串，并使用 <code>JSON.parse()</code> 将其解析为 JavaScript 对象。</p><h2 id="%E5%9C%A8-java-%E4%B8%AD%E8%A7%A3%E6%9E%90-url-%E5%8F%82%E6%95%B0" tabindex="-1">在 Java 中解析 URL 参数</h2><p>在 Java 中，我们可以使用 <code>java.net.URLDecoder</code> 类和 <code>java.util.Map</code> 接口来解析 URL 参数。以下是一个使用 Java 解析URL 参数的示例：</p><pre><code class="language-java">import java.io.UnsupportedEncodingException;import java.net.URLDecoder;import java.util.HashMap;import java.util.Map;public class Main {  public static void main(String[] args) throws UnsupportedEncodingException {    String urlString = &quot;http://localhost:3000/?data=%7B%22name%22%3A%22John%22%2C%22age%22%3A30%7D&quot;;    String[] urlParts = urlString.split(&quot;\\?&quot;);    String query = urlParts.length &gt; 1 ? urlParts[1] : &quot;&quot;;    Map&lt;String, String&gt; queryMap = new HashMap&lt;&gt;();    for (String param : query.split(&quot;&amp;&quot;)) {      String[] pair = param.split(&quot;=&quot;);      String key = URLDecoder.decode(pair[0], &quot;UTF-8&quot;);      String value = URLDecoder.decode(pair[1], &quot;UTF-8&quot;);      queryMap.put(key, value);    }    // 解析包含在 &#39;data&#39; 参数中的 JSON 字符串    String rawData = queryMap.get(&quot;data&quot;);    String json = URLDecoder.decode(rawData, &quot;UTF-8&quot;);    JSONObject myObject = new JSONObject(json);    // 执行其他操作...  }}</code></pre><p>在上面的示例中，我们首先将请求 URL 分为基础部分和查询字符串部分，然后将查询字符串参数解析为一个键值对的 Map 对象。然后，我们从 <code>data</code> 参数中获取包含 URL 编码的 JSON 字符串的原始数据，使用 <code>URLDecoder.decode()</code> 解码该字符串，并使用 <code>JSONObject</code> 类将其解析为 Java 对象。</p><h1 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h1><p>在前端使用 URL 参数传递 JSON 数据时，需要先将 JSON 数据转换为字符串并进行 URL 编码。在后端中解析 URL 参数时，需要先将 URL 编码的字符串解码为原始数据，并将其解析为相应的数据类型。不同的后端语言和框架可能有不同的解析方式，但是基本的原理都是相同的。</p>]]>
                    </description>
                    <pubDate>Wed, 26 Apr 2023 13:09:31 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[选择哪种Web服务器？WebLogic vs Undertow vs Tomcat vs Nginx对比分析！]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/xuan-ze-na-zhong-web-fu-wu-qi-weblogicvsundertowvstomcatvsnginx-dui-bi-fen-xi-</link>
                    <description>
                            <![CDATA[<h1 id="%E5%89%8D%E8%A8%80" tabindex="-1">前言</h1><p>WebLogic、Undertow、Tomcat和Nginx是常用的Web服务器和应用程序服务器。它们具有不同的功能、应用场景、优缺点等方面的特点，本文将对它们进行详细的比较。</p><h1 id="%E5%8A%9F%E8%83%BD%E6%AF%94%E8%BE%83" tabindex="-1">功能比较</h1><p>WebLogic是一个完整的JavaEE应用程序服务器，它具有强大的功能和灵活的配置。WebLogic支持分布式应用程序部署、负载均衡、高可用性、安全性等特性，适用于大型企业级Java应用程序。</p><p>Undertow是一个轻量级的Web服务器和应用程序服务器，它具有高性能和可扩展性的特点。Undertow支持HTTP、HTTPS、AJAX、WebSockets等协议，适用于构建高性能、低延迟的Web应用程序。</p><p>Tomcat是一个轻量级的Web服务器和应用程序服务器，它具有简单易用的特点。Tomcat支持Servlet、JSP等Java Web开发技术，适用于中小型Web应用程序。</p><p>Nginx是一个高性能的Web服务器和反向代理服务器，它具有高并发能力、低延迟和高可靠性的特点。Nginx支持负载均衡、反向代理、HTTP缓存等特性，适用于构建高性能、高并发、低延迟的Web应用程序。</p><h1 id="%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%E6%AF%94%E8%BE%83" tabindex="-1">应用场景比较</h1><p>WebLogic适用于大型企业级Java应用程序，例如电子商务、金融服务、电信等行业的应用程序。WebLogic具有出色的可扩展性、高可靠性和安全性，适用于对性能、可靠性和安全性有严格要求的应用程序。</p><p>Undertow适用于构建高性能、低延迟的Web应用程序，例如在线游戏、金融交易等需要快速响应的应用程序。Undertow具有轻量级、高性能和可扩展性的特点，适用于对性能有严格要求的应用程序。</p><p>Tomcat适用于中小型Web应用程序，例如博客、社交网络、企业内部应用程序等。Tomcat具有轻量级、易于使用和配置的特点，适用于对性能要求不是特别高的应用程序。</p><p>Nginx适用于构建高性能、高并发、低延迟的Web应用程序，例如电子商务、社交网络等需要支持大量并发用户访问的应用程序。Nginx具有高性能、高可靠性和可扩展性的特点，适用于对性能和可靠性有严格要求的应用程序。</p><h1 id="%E4%BC%98%E7%BC%BA%E7%82%B9%E6%AF%94%E8%BE%83" tabindex="-1">优缺点比较</h1><p>WebLogic的优点是具有出色的可扩展性、高可靠性和安全性。它支持JavaEE规范，可以满足大型企业级应用程序的需求。缺点是相对于其他服务器而言比较复杂，需要一定的学习成本和配置成本，同时也需要更多的硬件资源支持。</p><p>Undertow的优点是轻量级、高性能和可扩展性。它支持多种协议，适用于构建高性能、低延迟的Web应用程序。缺点是不支持JavaEE规范，无法满足大型企业级应用程序的需求，同时也缺乏成熟的生态系统和工具支持。</p><p>Tomcat的优点是轻量级、易于使用和配置。它支持Servlet、JSP等Java Web开发技术，适用于中小型Web应用程序。缺点是相对于其他服务器而言功能较为简单，不能满足大型企业级应用程序的需求。</p><p>Nginx的优点是高性能、高可靠性和可扩展性。它支持负载均衡、反向代理、HTTP缓存等特性，适用于构建高性能、高并发、低延迟的Web应用程序。缺点是不支持JavaEE规范，不能直接运行Java应用程序，需要结合其他服务器使用。</p><h1 id="%E6%94%AF%E6%8C%81%E7%9A%84%E5%B9%B3%E5%8F%B0" tabindex="-1">支持的平台</h1><ul><li>WebLogic：支持Windows、Linux、Solaris等平台。</li><li>Undertow：支持Windows、Linux、Mac OS X等平台。</li><li>Tomcat：支持Windows、Linux、Mac OS X等平台。</li><li>Nginx：支持Windows、Linux、Unix等平台。</li></ul><h1 id="%E6%94%AF%E6%8C%81%E7%9A%84%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80" tabindex="-1">支持的编程语言</h1><ul><li>WebLogic：支持Java。</li><li>Undertow：支持Java。</li><li>Tomcat：支持Java。</li><li>Nginx：支持C、C++、Perl、Python等编程语言。</li></ul><h1 id="%E7%AE%A1%E7%90%86%E5%92%8C%E7%9B%91%E6%8E%A7" tabindex="-1">管理和监控</h1><ul><li>WebLogic：具有完整的Web控制台和管理API，可以轻松管理和监控Web应用程序。</li><li>Undertow：提供JMX API和可配置的管理端点，但没有Web控制台。</li><li>Tomcat：提供管理和监控工具，例如Web控制台、管理界面和JMX API。</li><li>Nginx：提供基本的管理和监控工具，例如ngx_http_status_module和ngx_http_stub_status_module模块。</li></ul><h1 id="%E6%80%A7%E8%83%BD" tabindex="-1">性能</h1><ul><li>WebLogic：具有较高的性能，但相对较慢，适用于大型企业级应用程序。</li><li>Undertow：具有极高的性能和低延迟，适用于高性能Web应用程序。</li><li>Tomcat：具有较高的性能和低延迟，适用于中小型Web应用程序。</li><li>Nginx：具有极高的性能、低延迟和高并发能力，适用于大型Web应用程序。</li></ul><h1 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h1><p>WebLogic、Undertow、Tomcat和Nginx都是常用的Web服务器和应用程序服务器。它们具有不同的功能、应用场景、优缺点等方面的特点，选择合适的服务器需要根据具体的需求来决定。</p><p>如果需要构建大型企业级Java应用程序，可以选择WebLogic；如果需要构建高性能、低延迟的Web应用程序，可以选择Undertow；如果需要构建中小型Web应用程序，可以选择Tomcat；如果需要构建高性能、高并发、低延迟的Web应用程序，可以选择Nginx。</p><p>总之，选择合适的服务器可以提高应用程序的性能、可靠性和安全性，为用户提供更好的体验。</p>]]>
                    </description>
                    <pubDate>Fri, 07 Apr 2023 10:56:01 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Firewall vs iptables：什么是最好的Linux防火墙工具？]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/firewallvsiptables什么是最好的linux防火墙工具</link>
                    <description>
                            <![CDATA[<h1 id="%E5%89%8D%E8%A8%80" tabindex="-1">前言</h1><p>作为一名Linux管理员，保护服务器免受网络攻击是最重要的任务之一。Linux操作系统提供了许多防火墙工具，其中最常用的是iptables和Firewall。本文将比较Firewall和iptables之间的不同之处，并探讨哪个防火墙工具更适合您的需求。</p><h1 id="firewall%E5%92%8Ciptables%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F" tabindex="-1">Firewall和iptables是什么？</h1><p>iptables是一个Linux防火墙工具，它通过对网络数据包进行过滤和修改来控制网络访问。Firewall是新一代的Linux动态防火墙，它基于D-Bus消息系统，采用了Zone和Service的概念来管理网络访问。</p><h1 id="iptables%E4%BD%BF%E7%94%A8%E5%91%BD%E4%BB%A4" tabindex="-1">iptables使用命令</h1><ul><li>查看当前的iptables规则：iptables -L</li><li>清除当前的iptables规则：iptables -F</li><li>允许指定端口的流量通过：iptables -A INPUT -p tcp --dport [端口号] -j ACCEPT</li><li>阻止指定端口的流量通过：iptables -A INPUT -p tcp --dport [端口号] -j DROP</li><li>允许某个IP地址的流量通过：iptables -A INPUT -s [IP地址] -j ACCEPT</li><li>阻止某个IP地址的流量通过：iptables -A INPUT -s [IP地址] -j DROP</li></ul><h1 id="firewall%E4%BD%BF%E7%94%A8%E5%91%BD%E4%BB%A4" tabindex="-1">Firewall使用命令</h1><ul><li>查看Firewall状态：firewall-cmd --state</li><li>查看当前的Firewall规则：firewall-cmd --list-all</li><li>允许指定端口的流量通过：firewall-cmd --zone=public --add-port=[端口号]/tcp --permanent</li><li>阻止指定端口的流量通过：firewall-cmd --zone=public --remove-port=[端口号]/tcp --permanent</li><li>允许某个IP地址的流量通过：firewall-cmd --zone=public --add-source=[IP地址] --permanent</li><li>阻止某个IP地址的流量通过：firewall-cmd --zone=public --remove-source=[IP地址] --permanent</li></ul><h1 id="%E6%AF%94%E8%BE%83iptables%E5%92%8Cfirewall" tabindex="-1">比较iptables和Firewall</h1><h2 id="%E8%AF%AD%E6%B3%95%E5%92%8C%E8%A7%84%E5%88%99" tabindex="-1">语法和规则</h2><p>iptables使用基于命令行的语法，可以直接使用iptables命令来添加、修改和删除防火墙规则。而Firewall使用XML或JSON格式的配置文件，可以使用firewall-cmd命令行工具或图形界面进行管理。</p><h2 id="%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F" tabindex="-1">实现方式</h2><p>iptables是传统的Linux防火墙工具，而Firewall是新一代的动态防火墙。Firewall允许在运行时添加和删除规则，而不需要重启防火墙服务。</p><h2 id="%E7%AE%A1%E7%90%86%E5%92%8C%E9%85%8D%E7%BD%AE" tabindex="-1">管理和配置</h2><p>iptables可以通过编辑配置文件来管理和配置规则，也可以通过调用命令行工具来实现。而Firewall是一种动态防火墙，它允许在运行时添加和删除规则，而不需要重启防火墙服务。</p><h2 id="%E6%80%A7%E8%83%BD%E5%92%8C%E6%95%88%E7%8E%87" tabindex="-1">性能和效率</h2><p>iptables具有更高的性能和效率，可以处理更高的网络流量和更复杂的防火墙规则。而Firewall虽然具有动态性和易用性等优点，但其性能和效率不如iptables。</p><h1 id="iptables%E5%92%8Cfirewall%E7%9A%84%E7%94%9F%E6%95%88%E8%A7%84%E5%88%99" tabindex="-1">iptables和Firewall的生效规则</h1><p>对于iptables，当您添加或删除规则时，这些规则会立即生效，并在运行iptables -L命令时显示出来。但是，这些规则不会在系统重启后自动生效。为了保证规则在系统重启后仍然生效，您需要将这些规则保存到文件中，并确保在启动时加载该文件。您可以使用以下命令将当前iptables规则保存到文件中：</p><pre><code class="language-shell">iptables-save &gt; /etc/sysconfig/iptables</code></pre><p>在系统重启后，可以使用以下命令加载保存的规则：</p><pre><code class="language-shell">iptables-restore &lt; /etc/sysconfig/iptables</code></pre><p>对于Firewall，添加或删除规则时，这些规则不会立即生效，您需要运行以下命令使其生效：</p><pre><code class="language-shell">firewall-cmd --reload</code></pre><p>此命令会重新加载Firewall的规则，并应用任何更改。但是，请注意，如果您没有使用–permanent选项将规则永久保存，则在系统重启后，这些规则将被清除。为了确保规则在系统重启后仍然生效，您需要使用以下命令将规则永久保存：</p><pre><code class="language-shell">firewall-cmd --zone=public --add-port=8080/tcp --permanent</code></pre><p>此命令将添加一个允许端口8080的永久规则。在系统重启后，此规则将自动加载。</p><p>综上所述，无论您使用iptables或Firewall哪一个修改防火墙规则时，请注意保存规则，并确保它们在系统重启后仍然生效。</p><h1 id="%E5%93%AA%E4%B8%AA%E9%98%B2%E7%81%AB%E5%A2%99%E5%B7%A5%E5%85%B7%E6%9B%B4%E9%80%82%E5%90%88%E6%82%A8%E7%9A%84%E9%9C%80%E6%B1%82%EF%BC%9F" tabindex="-1">哪个防火墙工具更适合您的需求？</h1><p>如果您需要处理高流量和复杂规则的环境，则使用iptables是一个很好的选择。iptables具有更高的性能和效率，并且可以处理更复杂的防火墙规则。</p><p>如果您需要简单、易用和动态防火墙，则Firewall是一个很好的选择。Firewall具有动态性和易用性等优点，并允许在运行时添加和删除规则，而不需要重启防火墙服务。</p><h1 id="%E7%BB%93%E8%AE%BA" tabindex="-1">结论</h1><p>防火墙是保护服务器安全的关键。iptables和Firewall是Linux操作系统上最常用的防火墙工具，它们之间有许多不同之处。选择哪种防火墙工具取决于您的具体需求和偏好。无论您选择使用哪种工具，</p>]]>
                    </description>
                    <pubDate>Mon, 27 Mar 2023 23:39:06 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Nacos：1.0 vs. 2.0，你需要选择哪个版本来管理你的微服务？]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/nacos10vs20-ni-xu-yao-xuan-ze-na-ge-ban-ben-lai-guan-li-ni-de-wei-fu-wu-</link>
                    <description>
                            <![CDATA[<h1 id="%E5%BC%95%E8%A8%80" tabindex="-1">引言</h1><p>Nacos是一个开源的分布式配置中心和服务发现平台，它可以帮助开发者轻松管理微服务架构中的配置和服务注册。在Nacos的不断发展中，1.0版本和2.0版本都是非常重要的版本，本篇博客将对这两个版本进行介绍和比较。</p><h1 id="%E4%B8%80%E3%80%81nacos-1.0%E7%89%88%E6%9C%AC" tabindex="-1">一、Nacos 1.0版本</h1><p>Nacos 1.0版本于2019年3月发布，它是Nacos的第一个正式版本，也是经过多次测试和优化后的稳定版本。相较于之前的beta版本，Nacos 1.0版本有了很大的改进和优化，主要包括以下几个方面：</p><h2 id="1.-%E5%8A%9F%E8%83%BD%E5%AE%8C%E5%96%84" tabindex="-1">1. 功能完善</h2><p>Nacos 1.0版本在功能上相对完善，包括了配置中心、服务注册与发现、命名空间、健康检查等核心功能。此外，Nacos 1.0版本还增加了可插拔的扩展能力，可以方便地扩展各种插件，例如自定义的服务发现协议。</p><h2 id="2.-%E6%80%A7%E8%83%BD%E6%8F%90%E5%8D%87" tabindex="-1">2. 性能提升</h2><p>Nacos 1.0版本在性能上也有很大的提升，通过优化网络通信协议和数据存储方式，大大提高了系统的并发处理能力和吞吐量，可以满足更高的性能需求。</p><h2 id="3.-%E7%A8%B3%E5%AE%9A%E6%80%A7%E6%94%B9%E8%BF%9B" tabindex="-1">3. 稳定性改进</h2><p>Nacos 1.0版本在稳定性方面也进行了不少改进，通过增加监控和自动修复机制，可以更快地检测和修复系统故障，从而提高了系统的稳定性和可靠性。</p><h1 id="%E4%BA%8C%E3%80%81nacos-2.0%E7%89%88%E6%9C%AC" tabindex="-1">二、Nacos 2.0版本</h1><p>Nacos 2.0版本于2020年9月发布，相对于1.0版本，它的改进和优化更加突出，主要体现在以下几个方面：</p><h2 id="1.-%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%80%E8%87%B4%E6%80%A7" tabindex="-1">1. 分布式一致性</h2><p>Nacos 2.0版本引入了Raft算法，实现了分布式一致性，从而保证了集群环境下数据的强一致性和高可用性。</p><h2 id="2.-%E6%9B%B4%E5%A4%9A%E7%9A%84%E5%8A%9F%E8%83%BD%E6%94%AF%E6%8C%81" tabindex="-1">2. 更多的功能支持</h2><p>Nacos 2.0版本增加了更多的功能支持，例如DNS解析、动态配置刷新、访问控制等，为用户提供了更加全面的服务治理和配置管理能力。</p><h2 id="3.-%E6%9B%B4%E9%AB%98%E7%9A%84%E6%80%A7%E8%83%BD%E5%92%8C%E6%89%A9%E5%B1%95%E6%80%A7" tabindex="-1">3. 更高的性能和扩展性</h2><p>Nacos 2.0版本在性能和扩展性方面也有很大的提升，采用异步I/O、内存池等技术，大大提高了系统的处理能力和吞吐量。此外，Nacos 2.0版本还提供了更加灵活的插件机制，方便用户进行个性化定制和扩展。</p><h1 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h1><p>Nacos 1.0版本和2.0版本都是非常重要的版本，它们分别在不同的方面进行了优化和改进，为用户提供了更加全面和稳定的服务治理和配置管理能力。如果您是初次接触Nacos，建议选择最新版本2.0，以便获得更好的性能和更多的功能支持。但如果您的应用已经在Nacos 1.0版本上运行良好，也可以继续使用该版本，因为它已经经过多次测试和优化，具有很高的稳定性和可靠性。不管选择哪个版本，都需要根据实际业务场景和需求来进行选择和配置，以获得最佳的服务治理和配置管理效果。</p>]]>
                    </description>
                    <pubDate>Fri, 17 Mar 2023 11:13:41 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[当数据遇上响应式编程：Java应用中如何使用R2DBC访问关系型数据库？]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/dang-shu-ju-yu-shang-xiang-ying-shi-bian-cheng-java-ying-yong-zhong-ru-he-shi-yong-r2dbc-fang-wen-guan-xi-xing-shu-ju-ku-</link>
                    <description>
                            <![CDATA[<p>在当今的大数据时代，关系型数据库仍然是最常用的数据存储方式之一。Java是一种广泛使用的编程语言，也是访问关系型数据库的主要语言之一。在Java应用程序中，通常使用JDBC（Java Database Connectivity）API来访问数据库。但是，JDBC使用的同步/阻塞模型在处理高并发和大数据量的情况下可能会成为瓶颈，因此R2DBC（Reactive Relational Database Connectivity）在此时显得更加合适。</p><p>R2DBC是Java应用程序访问关系型数据库的一种新方式，它采用了响应式编程的思想，提供了异步、非阻塞的API，能够提高Java应用程序在高并发场景下的性能和可伸缩性。</p><p>在本文中，我们将介绍R2DBC的基本概念和原理，并提供一些使用R2DBC的示例。</p><h1 id="r2dbc%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%E5%92%8C%E5%8E%9F%E7%90%86" tabindex="-1">R2DBC的基本概念和原理</h1><p>R2DBC（Reactive Relational Database Connectivity）是一种基于异步、响应式编程模型的标准化关系型数据库连接API。R2DBC允许您使用响应式编程模型访问关系型数据库，这种编程模型通常用于处理大量并发请求、高吞吐量和低延迟场景。</p><p>R2DBC的主要设计目标是提供一种简单的异步、响应式编程模型，以及一种统一的方式来连接不同类型的关系型数据库。与传统的JDBC API不同，R2DBC使用反应流作为响应式编程模型的基础，提供一组异步操作符，以便您可以使用流式编程模型来执行数据库操作。</p><p>目前，R2DBC支持多种关系型数据库，包括MySQL、PostgreSQL、Microsoft SQL Server和H2数据库。在使用R2DBC时，您需要为您的数据库选择适当的R2DBC驱动程序，并按照驱动程序的要求进行配置。</p><h1 id="r2dbc%E6%8F%90%E4%BE%9B%E4%BA%86%E4%BB%A5%E4%B8%8B%E4%B8%BB%E8%A6%81%E7%89%B9%E6%80%A7" tabindex="-1">R2DBC提供了以下主要特性</h1><ul><li><p>异步执行：R2DBC使用异步编程模型，可以处理大量并发请求，提供高吞吐量和低延迟。</p></li><li><p>响应式编程模型：R2DBC基于反应流（Reactive Streams）标准，提供了一组异步操作符，可以使用流式编程模型来执行数据库操作。</p></li><li><p>标准化API：R2DBC提供了一种标准化的关系型数据库连接API，可以使用相同的API连接不同类型的关系型数据库。</p></li><li><p>轻量级：R2DBC是一个轻量级的API，它没有复杂的ORM框架或其他繁重的依赖。</p></li><li><p>无阻塞式I/O：R2DBC使用无阻塞式I/O操作，可以处理大量并发请求，并提供高吞吐量和低延迟。</p></li></ul><h1 id="%E4%BD%BF%E7%94%A8r2dbc%E7%9A%84%E7%A4%BA%E4%BE%8B" tabindex="-1">使用R2DBC的示例</h1><p>使用R2DBC来连接MySQL数据库，您需要执行以下步骤：</p><h2 id="%E6%AD%A5%E9%AA%A41%EF%BC%9A%E6%B7%BB%E5%8A%A0%E4%BE%9D%E8%B5%96%E9%A1%B9" tabindex="-1">步骤1：添加依赖项</h2><p>要在Java应用程序中使用R2DBC来访问MySQL数据库，首先需要将R2DBC MySQL依赖项添加到项目中。我们可以通过以下Maven依赖项将R2DBC MySQL引入我们的项目中：</p><pre><code class="language-xml">&lt;dependency&gt;    &lt;groupId&gt;dev.miku&lt;/groupId&gt;    &lt;artifactId&gt;r2dbc-mysql&lt;/artifactId&gt;    &lt;version&gt;0.8.8.RELEASE&lt;/version&gt;&lt;/dependency&gt;</code></pre><h2 id="%E6%AD%A5%E9%AA%A42%EF%BC%9A%E9%85%8D%E7%BD%AE%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5" tabindex="-1">步骤2：配置数据库连接</h2><p>在使用R2DBC访问MySQL数据库之前，我们需要先配置数据库连接。下面是一个示例配置：</p><pre><code class="language-java">@Configurationpublic class R2dbcConfiguration {    @Bean    public ConnectionFactory connectionFactory() {        return new MysqlConnectionFactory(            ConnectionFactoryOptions.builder()                .option(DRIVER, &quot;mysql&quot;)                .option(HOST, &quot;localhost&quot;)                .option(USER, &quot;username&quot;)                .option(PASSWORD, &quot;password&quot;)                .option(DATABASE, &quot;database&quot;)                .build()        );    }}</code></pre><p>在上面的示例中，我们使用<code>MysqlConnectionFactory</code>类创建MySQL连接工厂。同时，我们使用<code>ConnectionFactoryOptions</code>类配置了连接选项，包括数据库驱动程序、主机、用户名、密码和数据库名称等。</p><h2 id="%E6%AD%A5%E9%AA%A43%EF%BC%9A%E4%BD%BF%E7%94%A8%E8%BF%9E%E6%8E%A5%E5%B7%A5%E5%8E%82%E5%88%9B%E5%BB%BA%E8%BF%9E%E6%8E%A5" tabindex="-1">步骤3：使用连接工厂创建连接</h2><p>一旦我们已经配置好了数据库连接，我们可以使用连接工厂创建一个新的数据库连接。以下是一个示例：</p><pre><code class="language-java">public class UserRepository {    private final ConnectionFactory connectionFactory;    public UserRepository(ConnectionFactory connectionFactory) {        this.connectionFactory = connectionFactory;    }    public Flux&lt;User&gt; findAll() {        return Mono.from(connectionFactory.create())            .flatMapMany(connection -&gt;                Flux.from(connection.createStatement(&quot;SELECT * FROM users&quot;).execute())                    .flatMap(result -&gt; result.map((row, rowMetadata) -&gt;                        new User(row.get(&quot;id&quot;, Long.class), row.get(&quot;name&quot;, String.class))                    ))                    .doFinally((signalType) -&gt; Mono.from(connection.close()).subscribe())            );    }}</code></pre><p>在上面的示例中，我们创建了一个<code>UserRepository</code>类，并使用<code>MysqlConnectionFactory</code>类创建MySQL连接工厂。我们使用<code>Mono.from(connectionFactory.create())</code>方法创建一个新的数据库连接。接下来，我们使用<code>Flux.from(connection.createStatement(&quot;SELECT * FROM users&quot;).execute())</code>方法创建一个Flux，该Flux将使用SQL查询语句从数据库中检索所有用户记录。我们使用<code>flatMap()</code>方法将结果转换为我们的<code>User</code>对象，并将其作为<code>Flux</code>对象返回。最后，我们使用<code>doFinally()</code>方法关闭数据库连接。</p><h2 id="%E6%AD%A5%E9%AA%A44%EF%BC%9A%E4%BD%BF%E7%94%A8r2dbc%E5%9C%A8java%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E4%B8%AD%E8%AE%BF%E9%97%AEmysql%E6%95%B0%E6%8D%AE%E5%BA%93" tabindex="-1">步骤4：使用R2DBC在Java应用程序中访问MySQL数据库</h2><p>我们现在已经配置了数据库连接，并创建了一个用于访问数据库的<code>UserRepository</code>类。我们可以在Java应用程序中使用此类来访问MySQL数据库。以下是一个示例：</p><pre><code class="language-java">public class Application {    public static void main(String[] args) {        ApplicationContext context = new AnnotationConfigApplicationContext(R2dbcConfiguration.class);        UserRepository userRepository = context.getBean(UserRepository.class);        userRepository.findAll()            .subscribe(user -&gt; System.out.println(&quot;User: &quot; + user));    }}</code></pre><p>在上面的示例中，我们创建了一个<code>Application</code>类，并在其中创建了一个<code>UserRepository</code>实例。我们调用<code>userRepository.findAll()</code>方法来检索所有用户记录，并在控制台上打印每个用户的名称。最后，我们使用<code>subscribe()</code>方法订阅<code>Flux</code>对象。</p><h1 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h1><p>R2DBC是一种基于响应式编程的数据库访问API，它可以提高Java应用程序在高并发场景下的性能和可伸缩性。使用R2DBC可以让程序员使用异步、非阻塞的API访问关系型数据库，从而充分发挥计算机的CPU和内存资源。</p><p>在使用R2DBC时，需要遵循基本步骤，包括添加R2DBC依赖项、配置数据库连接、使用连接工厂创建连接，以及执行查询或更新等操作。通过这些步骤，程序员可以编写高效、可伸缩的Java应用程序，从而更好地应对大规模数据处理和高并发访问的场景。</p><p>总的来说，R2DBC是Java应用程序中非常有用的工具，可以帮助开发者提高程序的性能和可伸缩性。</p>]]>
                    </description>
                    <pubDate>Thu, 16 Mar 2023 14:23:09 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[当分布式遇上一致性：Raft、SofaJRaft和Distro协议大比拼]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/dang-fen-bu-shi-yu-shang-yi-zhi-xing-raftsofajraft-he-distro-xie-yi-da-bi-pin</link>
                    <description>
                            <![CDATA[<p>今天，我学习nacos的源码，看到了distro协议，于是本篇博客就由此而来了，通过网上查找的资料我大体整理了下，下面是整理后的结果。</p><h1 id="%E5%BC%95%E8%A8%80" tabindex="-1">引言</h1><p>分布式系统是由多个计算机节点组成的系统，这些节点通过网络相互连接，并协同工作来实现一个共同的目标。在分布式系统中，数据的一致性是一个非常重要的问题。分布式一致性算法可以帮助我们解决这个问题。本文将介绍三种分布式一致性算法：distro协议、sofajraft协议、raft协议，并讨论它们的适用场景和特点。</p><h1 id="raft%E5%8D%8F%E8%AE%AE" tabindex="-1">Raft协议</h1><p>Raft是一种分布式一致性算法，由Stanford大学的Diego Ongaro和John Ousterhout于2013年提出。Raft算法的主要目标是提供一种易于理解和实现的分布式一致性算法。Raft算法具有良好的可读性和易于理解的特点，使得它容易被人们理解和实现。Raft算法通过领导选举、日志复制、一致性检查点等基础功能，保证了分布式系统中数据的一致性。</p><h1 id="sofajraft%E5%8D%8F%E8%AE%AE" tabindex="-1">SofaJRaft协议</h1><p>SofaJRaft是一种基于Raft协议的改进版本。SofaJRaft在Raft协议的基础上增加了一些特性，例如动态配置、快照等，以适应更加复杂的场景需求。SofaJRaft算法的设计目标是提供一个高性能、高可用、易于扩展的分布式一致性算法。SofaJRaft算法在性能和可扩展性方面优于Raft协议，适用于更为复杂的分布式系统，例如分布式存储、分布式数据库等。</p><h1 id="distro%E5%8D%8F%E8%AE%AE" tabindex="-1">Distro协议</h1><p>Distro协议是基于SofaJRaft协议的一种改进版本。Distro协议在SofaJRaft协议的基础上进一步优化，例如增加了故障转移功能，提高了容错性能。Distro协议的设计目标是提供一个高可靠、高性能、易于扩展的分布式一致性算法。Distro协议适用于更加严苛的分布式系统环境，例如金融、电信等领域的应用。</p><h1 id="%E4%B8%89%E7%A7%8D%E5%8D%8F%E8%AE%AE%E6%AF%94%E8%BE%83" tabindex="-1">三种协议比较</h1><p>Raft协议、SofaJRaft协议和Distro协议都是分布式一致性算法，它们之间有以下的不同和优势：</p><ol><li><p>Raft协议的可读性和易于理解性更好，适用于一些小规模的分布式系统。</p></li><li><p>SofaJRaft协议增加了一些特性，例如动态配置、快照等，适用于更为复杂的分布式系统，例如分布式存储、分布式数据库等。</p></li><li><p>Distro协议在SofaJRaft协议的基础上增加了故障转移功能，提高了容错性能，适用于更加严苛的分布式系统环境，例如金融、电信等领域的应用。</p></li></ol><h1 id="%E5%85%B1%E6%80%A7" tabindex="-1">共性</h1><ol><li><p>都使用领导者选举机制，通过选举一个领导者来管理整个系统。</p></li><li><p>都使用日志复制机制，通过复制日志来实现数据的一致性。</p></li><li><p>都可以实现线性一致性。</p></li><li><p>都可以扩展到多个节点。</p></li></ol><p>总之，选择合适的分布式一致性算法需要综合考虑系统规模、复杂度、容错性要求等因素。同时，需要注意算法的实现、性能、可维护性等方面的问题。</p>]]>
                    </description>
                    <pubDate>Wed, 15 Mar 2023 14:28:54 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[详细的Python Flask的操作]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/xiang-xi-de-pythonflask-de-cao-zuo</link>
                    <description>
                            <![CDATA[<p>本篇文章是<a href="https://www.w3cschool.cn/minicourse/play/pyflask" target="_blank">Python Flask 建站框架入门课程_编程实战微课_w3cschool</a>微课的学习笔记，根据课程整理而来，本人使用版本如下：</p><table><thead><tr><th>Python</th><th>3.10.0</th></tr></thead><tbody><tr><td>Flask</td><td>2.2.2</td></tr></tbody></table><h1 id="%E7%AE%80%E4%BB%8B" tabindex="-1">简介</h1><ul><li><p>Flask是一个轻量级的可定制的web框架</p></li><li><p>Flask 可以很好地结合MVC模式进行开发</p></li><li><p>Flask还有很强的很强的扩展性和兼容性</p></li></ul><h1 id="%E6%A0%B8%E5%BF%83%E5%87%BD%E6%95%B0%E5%BA%93" tabindex="-1">核心函数库</h1><p>Flask主要包括Werkzeug和Jinja2两个核心函数库，它们分别负责业务处理和安全方面的功能，这些基础函数为web项目开发过程提供了丰富的基础组件。</p><h2 id="werkzeug" tabindex="-1">Werkzeug</h2><p>Werkzeug库十分强大，功能比较完善，支持URL路由请求集成，一次可以响应多个用户的访问请求；</p><p>支持Cookie和会话管理，通过身份缓存数据建立长久连接关系，并提高用户访问速度；支持交互式Javascript调试，提高用户体验；</p><p>可以处理HTTP基本事务，快速响应客户端推送过来的访问请求。</p><h2 id="jinja2" tabindex="-1">Jinja2</h2><p>Jinja2库支持自动HTML转移功能，能够很好控制外部黑客的脚本攻击；</p><p>系统运行速度很快，页面加载过程会将源码进行编译形成python字节码，从而实现模板的高效运行；</p><p>模板继承机制可以对模板内容进行修改和维护，为不同需求的用户提供相应的模板。</p><h1 id="%E5%AE%89%E8%A3%85" tabindex="-1">安装</h1><p>通过pip安装即可</p><pre><code class="language-batch">pip install Flask# pip3pip3 install Flask</code></pre><h1 id="%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84" tabindex="-1">目录结构</h1><h2 id="%E6%96%B0%E9%A1%B9%E7%9B%AE%E5%88%9B%E5%BB%BA%E5%90%8E%E7%9A%84%E7%BB%93%E6%9E%84" tabindex="-1">新项目创建后的结构</h2><p><img src="https://img.huangge1199.cn/blog/pythonFlask/2023-02-14-17-10-32-image.png" alt="" /></p><p>static文件夹：存放静态文件，比如css、js、图片等</p><p>templates文件夹：模板文件目录</p><p><a href="http://app.py" target="_blank">app.py</a>：应用启动程序</p><h1 id="%E8%8E%B7%E5%8F%96url%E5%8F%82%E6%95%B0" tabindex="-1">获取URL参数</h1><h2 id="%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89url%E5%8F%82%E6%95%B0" tabindex="-1">列出所有URL参数</h2><p><code>request.args.__str__()</code></p><pre><code class="language-python">from flask import Flask, requestapp = Flask(__name__)@app.route(&#39;/&#39;)def hello_world():  # put application&#39;s code here    return request.args.__str__()if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>在浏览器中访问<code>http://127.0.0.1:5000/?name=Loen&amp;age&amp;app=ios&amp;app=android</code>，将显示：</p><pre><code class="language-">ImmutableMultiDict([(&#39;name&#39;, &#39;Loen&#39;), (&#39;age&#39;, &#39;&#39;), (&#39;app&#39;, &#39;ios&#39;), (&#39;app&#39;, &#39;android&#39;)])</code></pre><h2 id="%E5%88%97%E5%87%BA%E6%B5%8F%E8%A7%88%E5%99%A8%E4%BC%A0%E7%BB%99%E6%88%91%E4%BB%AC%E7%9A%84flask%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%95%B0%E6%8D%AE" tabindex="-1">列出浏览器传给我们的Flask服务的数据</h2><pre><code class="language-python">from flask import Flask, requestapp = Flask(__name__)@app.route(&#39;/&#39;)def hello_world():  # put application&#39;s code here    # 列出访问地址    print(request.path)    # 列出访问地址及参数    print(request.full_path)    return request.args.__str__()if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>在浏览器中访问<code>http://127.0.0.1:5000/?name=Loen&amp;age&amp;app=ios&amp;app=android</code>，控制台中显示</p><pre><code class="language-">//?name=Loen&amp;age&amp;app=ios&amp;app=android</code></pre><h2 id="%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E7%9A%84%E5%8F%82%E6%95%B0%E5%80%BC" tabindex="-1">获取指定的参数值</h2><pre><code class="language-python">from flask import Flask, requestapp = Flask(__name__)@app.route(&#39;/&#39;)def hello_world():  # put application&#39;s code here    return request.args.get(&#39;name&#39;)if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>在浏览器中访问<code>http://127.0.0.1:5000/?name=Loen&amp;age&amp;app=ios&amp;app=android</code>，将显示：</p><pre><code class="language-">Loen</code></pre><h2 id="%E5%A4%84%E7%90%86%E5%A4%9A%E5%80%BC" tabindex="-1">处理多值</h2><pre><code class="language-pythoon">from flask import Flask, requestapp = Flask(__name__)@app.route(&#39;/&#39;)def hello_world():  # put application&#39;s code here    r = request.args.getlist(&#39;app&#39;)  # 返回一个list    return rif __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>在浏览器中访问<code>http://127.0.0.1:5000/?name=Loen&amp;age&amp;app=ios&amp;app=android</code>，将显示：</p><pre><code class="language-">[  &quot;ios&quot;,  &quot;android&quot;]</code></pre><h1 id="%E8%8E%B7%E5%8F%96post%E6%96%B9%E6%B3%95%E4%BC%A0%E9%80%81%E7%9A%84%E6%95%B0%E6%8D%AE" tabindex="-1">获取POST方法传送的数据</h1><p>作为一种HTTP请求方法，POST用于向指定的资源提交要被处理的数据。</p><p>我们在某些时候不适合将数据放到URL参数中，密或者数据太多，浏览器不一定支持太长长度的URL。这时，一般使用POST方法。</p><p>本文章使用python的requests库模拟浏览器。</p><p>安装命令：</p><pre><code class="language-batch">pip install requests</code></pre><h2 id="%E7%9C%8Bpost%E6%95%B0%E6%8D%AE%E5%86%85%E5%AE%B9" tabindex="-1">看POST数据内容</h2><p>app.py代码如下：</p><pre><code class="language-python">from flask import Flask, requestapp = Flask(__name__)@app.route(&#39;/register&#39;, methods=[&#39;POST&#39;])def register():    print(request.headers)    print(request.stream.read())    return &#39;welcome&#39;if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>register.py代码如下：</p><pre><code class="language-python">import requestsif __name__ == &#39;__main__&#39;:    user_info = {&#39;name&#39;: &#39;Loen&#39;, &#39;password&#39;: &#39;loveyou&#39;}    r = requests.post(&quot;http://127.0.0.1:5000/register&quot;, data=user_info)    print(r.text)</code></pre><p>运行<code>app.py</code>，然后运行<code>register.py</code>。</p><p><code>register.py</code>将输出：</p><pre><code class="language-">welcome</code></pre><p><code>app.py</code>将输出：</p><pre><code class="language-">Host: 127.0.0.1:5000User-Agent: python-requests/2.28.2Accept-Encoding: gzip, deflateAccept: */*Connection: keep-aliveContent-Length: 26Content-Type: application/x-www-form-urlencodedb&#39;name=Loen&amp;password=loveyou&#39;127.0.0.1 - - [14/Feb/2023 21:12:17] &quot;POST /register HTTP/1.1&quot; 200 -</code></pre><h2 id="%E8%A7%A3%E6%9E%90post%E6%95%B0%E6%8D%AE" tabindex="-1">解析POST数据</h2><p>app.py代码如下：</p><pre><code class="language-python">from flask import Flask, requestapp = Flask(__name__)@app.route(&#39;/register&#39;, methods=[&#39;POST&#39;])def register():    # print(request.stream.read()) # 不要用，否则下面的form取不到数据    print(request.form)    print(request.form[&#39;name&#39;])    print(request.form.get(&#39;name&#39;))    print(request.form.getlist(&#39;name&#39;))    print(request.form.get(&#39;nickname&#39;, default=&#39;little apple&#39;))    return &#39;welcome&#39;if __name__ == &#39;__main__&#39;:    app.run(port=5000, debug=True)</code></pre><p>register.py代码不变，运行<code>app.py</code>，然后运行<code>register.py</code>。</p><p><code>register.py</code>将输出：</p><pre><code class="language-">welcome</code></pre><p><code>app.py</code>将输出：</p><pre><code class="language-">ImmutableMultiDict([(&#39;name&#39;, &#39;Loen&#39;), (&#39;password&#39;, &#39;loveyou&#39;)])LoenLoen[&#39;Loen&#39;]little apple</code></pre><p>request.form会自动解析数据。</p><p>request.form[‘name’]和request.form.get(‘name’)都可以获取name对应的值。</p><p>request.form.get()可以为参数default指定值以作为默认值。</p><h2 id="%E8%8E%B7%E5%8F%96post%E4%B8%AD%E7%9A%84%E5%88%97%E8%A1%A8%E6%95%B0%E6%8D%AE" tabindex="-1">获取POST中的列表数据</h2><p>app.py代码如下：</p><pre><code class="language-python">from flask import Flask, requestapp = Flask(__name__)@app.route(&#39;/register&#39;, methods=[&#39;POST&#39;])def register():    # print(request.stream.read()) # 不要用，否则下面的form取不到数据    print(request.form.getlist(&#39;name&#39;))    return &#39;welcome&#39;if __name__ == &#39;__main__&#39;:    app.run(port=5000, debug=True)</code></pre><p>register.py代码如下：</p><pre><code class="language-python">import requestsif __name__ == &#39;__main__&#39;:    user_info = {&#39;name&#39;: [&#39;Loen&#39;, &#39;Alan&#39;], &#39;password&#39;: &#39;loveyou&#39;}    r = requests.post(&quot;http://127.0.0.1:5000/register&quot;, data=user_info)    print(r.text)</code></pre><p>运行<code>app.py</code>，然后运行<code>register.py</code>。</p><p><code>register.py</code>将输出：</p><pre><code class="language-">welcome</code></pre><p><code>app.py</code>将输出：</p><pre><code class="language-">[&#39;Loen&#39;, &#39;Alan&#39;]</code></pre><h1 id="%E5%A4%84%E7%90%86%E5%92%8C%E5%93%8D%E5%BA%94json%E6%95%B0%E6%8D%AE" tabindex="-1">处理和响应JSON数据</h1><h2 id="%E5%A4%84%E7%90%86json%E6%95%B0%E6%8D%AE" tabindex="-1">处理JSON数据</h2><p>如果POST的数据是JSON格式，request.json会自动将json数据转换成Python类型（字典或者列表）。</p><p>app.py代码如下：</p><pre><code class="language-python">from flask import Flask, requestapp = Flask(__name__)@app.route(&#39;/add&#39;, methods=[&#39;POST&#39;])def add():    print(type(request.json))    print(request.json)    result = request.json[&#39;n1&#39;] + request.json[&#39;n2&#39;]    return str(result)if __name__ == &#39;__main__&#39;:    app.run(port=5000, debug=True)</code></pre><p>register.py代码如下：</p><pre><code class="language-python">import requestsif __name__ == &#39;__main__&#39;:    json_data = {&#39;n1&#39;: 5, &#39;n2&#39;: 3}    r = requests.post(&quot;http://127.0.0.1:5000/add&quot;, json=json_data)    print(r.text)</code></pre><p>运行<code>app.py</code>，然后运行<code>register.py</code>。</p><p><code>register.py</code>将输出：</p><pre><code class="language-">8</code></pre><p><code>app.py</code>将输出：</p><pre><code class="language-">&lt;class &#39;dict&#39;&gt;{&#39;n1&#39;: 5, &#39;n2&#39;: 3}</code></pre><h2 id="%E5%93%8D%E5%BA%94json%E6%95%B0%E6%8D%AE%EF%BC%88response%EF%BC%89" tabindex="-1">响应JSON数据（Response）</h2><p>app.py代码如下：</p><pre><code class="language-python">import jsonfrom flask import Flask, request, Responseapp = Flask(__name__)@app.route(&#39;/add&#39;, methods=[&#39;POST&#39;])def add():    result = {&#39;sum&#39;: request.json[&#39;n1&#39;] + request.json[&#39;n2&#39;]}    return Response(json.dumps(result), mimetype=&#39;application/json&#39;)if __name__ == &#39;__main__&#39;:    app.run(port=5000, debug=True)</code></pre><p>register.py代码如下：</p><pre><code class="language-python">import requestsif __name__ == &#39;__main__&#39;:    json_data = {&#39;n1&#39;: 5, &#39;n2&#39;: 3}    r = requests.post(&quot;http://127.0.0.1:5000/add&quot;, json=json_data)    print(r.headers)    print(r.text)</code></pre><p>运行<code>app.py</code>，然后运行<code>register.py</code>。</p><p><code>register.py</code>将输出：</p><pre><code class="language-">/home/huangge1199/PycharmProjects/flaskProject/venv/bin/python /home/huangge1199/PycharmProjects/flaskProject/register.py {&#39;Server&#39;: &#39;Werkzeug/2.2.2 Python/3.7.3&#39;, &#39;Date&#39;: &#39;Tue, 14 Feb 2023 13:37:49 GMT&#39;, &#39;Content-Type&#39;: &#39;application/json&#39;, &#39;Content-Length&#39;: &#39;10&#39;, &#39;Connection&#39;: &#39;close&#39;}{&quot;sum&quot;: 8}</code></pre><h2 id="%E5%93%8D%E5%BA%94json%E6%95%B0%E6%8D%AE%EF%BC%88jsonify%EF%BC%89" tabindex="-1">响应JSON数据（jsonify）</h2><p>app.py中app()返回时使用下面的内容，效果同之前一样</p><pre><code class="language-python">return jsonify(result)</code></pre><h1 id="%E4%B8%8A%E4%BC%A0%E8%A1%A8%E5%8D%95" tabindex="-1">上传表单</h1><p>用 Flask 处理文件上传很简单，只要确保你没忘记在 HTML 表单中设置 enctype=“multipart/form-data” 属性，不然你的浏览器根本不会发送文件。</p><p>安装响应的库<code>werkzeug</code></p><pre><code class="language-batch">pip install werkzeug</code></pre><p>目录结构：</p><p><img src="https://img.huangge1199.cn/blog/pythonFlask/2023-02-15-15-10-19-image.png" alt="" /></p><p>app.py代码如下：</p><pre><code class="language-python">from flask import Flask, requestfrom werkzeug.utils import secure_filenameimport osapp = Flask(__name__)# 文件上传目录app.config[&#39;UPLOAD_FOLDER&#39;] = &#39;static/uploads/&#39;# 支持的文件格式app.config[&#39;ALLOWED_EXTENSIONS&#39;] = {&#39;png&#39;, &#39;jpg&#39;, &#39;jpeg&#39;, &#39;gif&#39;}  # 集合类型# 判断文件名是否是我们支持的格式def allowed_file(filename):    return &#39;.&#39; in filename and \        filename.rsplit(&#39;.&#39;, 1)[1] in app.config[&#39;ALLOWED_EXTENSIONS&#39;]@app.route(&#39;/upload&#39;, methods=[&#39;POST&#39;])def upload():    upload_file = request.files[&#39;image&#39;]    if upload_file and allowed_file(upload_file.filename):  # 上传前文件在客户端的文件名        filename = secure_filename(upload_file.filename)        # 将文件保存到 static/uploads 目录，文件名同上传时使用的文件名        upload_file.save(os.path.join(app.root_path, app.config[&#39;UPLOAD_FOLDER&#39;], filename))        return &#39;info is &#39; + request.form.get(&#39;info&#39;, &#39;&#39;) + &#39;. success&#39;    else:        return &#39;failed&#39;if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>register.py代码如下：</p><pre><code class="language-python">import requestsif __name__ == &quot;__main__&quot;:    file_data = {&#39;image&#39;: open(&#39;flask.png&#39;, &#39;rb&#39;)}    user_info = {&#39;info&#39;: &#39;flask&#39;}    r = requests.post(&quot;http://127.0.0.1:5000/upload&quot;, data=user_info, files=file_data)    print(r.text)</code></pre><p>运行<code>app.py</code>，然后运行<code>register.py</code>，这时候文件已经上传到了指定目录中</p><p><img src="https://img.huangge1199.cn/blog/pythonFlask/2023-02-15-15-11-43-image.png" alt="" /></p><p>要控制上产文件的大小，可以设置请求实体的大小，代码如下：</p><pre><code class="language-python">app.config[&#39;MAX_CONTENT_LENGTH&#39;] = 16 * 1024 * 1024 #16MB</code></pre><p>获取上传文件的内容，代码如下：</p><pre><code class="language-python">file_content = request.files[&#39;image&#39;].stream.read()</code></pre><h1 id="restful-url" tabindex="-1">Restful URL</h1><p>Restful URL可以看做是对 URL 参数的替代</p><h2 id="%E5%8F%98%E9%87%8F%E8%A7%84%E5%88%99" tabindex="-1">变量规则</h2><p>写法如下：</p><pre><code class="language-python">@app.route(&#39;/user/&lt;username&gt;/friends&#39;)</code></pre><h2 id="%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B" tabindex="-1">转换类型</h2><p>使用 Restful URL 得到的变量默认为str对象。我们可以用flask内置的转换机制，即在route中指定转换类型，写法如下：</p><pre><code class="language-python">@app.route(&#39;/page/&lt;int:num&gt;&#39;)</code></pre><p>有3个默认的转换器：</p><ul><li><p>int：接受整数</p></li><li><p>float：同 int ，但是接受浮点数</p></li><li><p>path：和默认的相似，但也接受斜线</p></li></ul><h2 id="%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BD%AC%E6%8D%A2%E5%99%A8" tabindex="-1">自定义转换器</h2><p>自定义的转换器是一个继承werkzeug.routing.BaseConverter的类，修改to_python和to_url方法即可。</p><p>to_python方法用于将url中的变量转换后供被<code>@app.route</code>包装的函数使用，to_url方法用于flask.url_for中的参数转换。</p><p>下面是一个示例：</p><pre><code class="language-python">from flask import Flask, url_forfrom werkzeug.routing import BaseConverterclass MyIntConverter(BaseConverter):    def __init__(self, url_map):        super(MyIntConverter, self).__init__(url_map)    def to_python(self, value):        return int(value)    def to_url(self, value):        return value * 2app = Flask(__name__)app.url_map.converters[&#39;my_int&#39;] = MyIntConverter@app.route(&#39;/page/&lt;my_int:num&gt;&#39;)def page(num):    print(num)    print(url_for(&#39;page&#39;, num=&#39;145&#39;))  # page 对应的是 page函数 ，num 对应对应&#96;/page/&lt;my_int:num&gt;&#96;中的num，必须是str    return &#39;hello world&#39;if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>运行<code>app.py</code>，浏览器访问<code>http://127.0.0.1:5000/page/28</code>后，<code>app.py</code>的输出信息是：</p><pre><code class="language-">28/page/145145</code></pre><h1 id="%E4%BD%BF%E7%94%A8url_for%E7%94%9F%E6%88%90%E9%93%BE%E6%8E%A5" tabindex="-1">使用url_for生成链接</h1><p>工具函数<code>url_for</code>可以让你以软编码的形式生成url，提供开发效率。</p><p>例子<code>app.py</code>代码如下：</p><pre><code class="language-python">from flask import Flask, url_forapp = Flask(__name__)@app.route(&#39;/&#39;)def hello_world():    pass@app.route(&#39;/user/&lt;name&gt;&#39;)def user(name):    pass@app.route(&#39;/page/&lt;int:num&gt;&#39;)def page(num):    pass@app.route(&#39;/test&#39;)def test():    print(url_for(&#39;test&#39;))    print(url_for(&#39;user&#39;, name=&#39;loen&#39;))    print(url_for(&#39;page&#39;, num=1, q=&#39;welcome to w3c 15%2&#39;))    print(url_for(&#39;static&#39;, filename=&#39;uploads/flask.png&#39;))    return &#39;Hello&#39;if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>运行<code>app.py</code>。然后在浏览器中访问<code>http://127.0.0.1:5000/test</code>，<code>server.py</code>控制台将输出以下信息：</p><pre><code class="language-">/test/user/loen/page/1?q=welcome+to+w3c+15%252/static/uploads/flask.jpg</code></pre><h1 id="%E4%BD%BF%E7%94%A8redirect%E9%87%8D%E5%AE%9A%E5%90%91%E7%BD%91%E5%9D%80" tabindex="-1">使用redirect重定向网址</h1><p>在浏览器中访问<code>http://127.0.0.1:5000/old</code>，浏览器的url会变成<code>http://127.0.0.1:5000/new</code>，并显示，<code>app.py</code>代码如下：</p><pre><code class="language-python">from flask import Flask, url_for, redirectapp = Flask(__name__)@app.route(&#39;/old&#39;)def old():    print(&#39;this is old&#39;)    return redirect(url_for(&#39;new&#39;))@app.route(&#39;/new&#39;)def new():    print(&#39;this is new&#39;)    return &#39;this is new&#39;if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>运行<code>app.py</code>，然后在浏览器中访问<code>http://127.0.0.1:5000/old</code></p><p>浏览器显示：</p><pre><code class="language-">this is new</code></pre><p>控制台显示：</p><pre><code class="language-">this is oldthis is new</code></pre><h1 id="%E8%87%AA%E5%AE%9A%E4%B9%89404" tabindex="-1">自定义404</h1><h2 id="%E5%A4%84%E7%90%86http%E9%94%99%E8%AF%AF" tabindex="-1">处理HTTP错误</h2><p>要处理HTTP错误，可以使用<code>flask.abort</code>函数。</p><p><code>app.py</code>代码如下：</p><pre><code class="language-python">from flask import Flask, abortapp = Flask(__name__)@app.route(&#39;/user&#39;)def user():    abort(401)  # Unauthorized 未授权    print(&#39;Unauthorized, 请先登录&#39;)if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>运行<code>app.py</code>，然后在浏览器中访问<code>http://127.0.0.1:5000/user</code></p><p>浏览器显示：</p><p><img src="https://img.huangge1199.cn/blog/pythonFlask/2023-02-15-17-26-19-image.png" alt="" /></p><h2 id="%E8%87%AA%E5%AE%9A%E4%B9%89%E9%94%99%E8%AF%AF%E9%A1%B5%E9%9D%A2" tabindex="-1">自定义错误页面</h2><p>page_unauthorized 函数返回的是一个元组，401 代表HTTP 响应状态码。</p><p>如果省略401，则响应状态码会变成默认的 200。</p><p><code>app.py</code>代码如下：</p><pre><code class="language-python">from flask import Flask, abort, render_template_stringapp = Flask(__name__)@app.route(&#39;/user&#39;)def user():    abort(401)  # Unauthorized@app.errorhandler(401)def page_unauthorized(error):    return render_template_string(&#39;&lt;h1&gt; Unauthorized &lt;/h1&gt;&lt;h2&gt;{{ error_info }}&lt;/h2&gt;&#39;, error_info=error), 401if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p>运行<code>app.py</code>，然后在浏览器中访问<code>http://127.0.0.1:5000/user</code></p><p>浏览器显示：</p><p><img src="https://img.huangge1199.cn/blog/pythonFlask/2023-02-15-17-29-44-image.png" alt="" /></p><h1 id="%E7%94%A8%E6%88%B7%E4%BC%9A%E8%AF%9D" tabindex="-1">用户会话</h1><p>session 用来记录用户的登录状态，一般基于cookie实现。</p><p><code>app.py</code>代码如下：</p><pre><code class="language-python">from flask import Flask, render_template_string, request, session, redirect, url_forapp = Flask(__name__)app.secret_key = &#39;LoenDSdtj\9bX#%@!!*(0&amp;^%)&#39;@app.route(&#39;/login&#39;)def login():    page = &#39;&#39;&#39;    &lt;form action=&quot;{{ url_for(&#39;do_login&#39;) }}&quot; method=&quot;post&quot;&gt;        &lt;p&gt;name: &lt;input type=&quot;text&quot; name=&quot;user_name&quot; /&gt;&lt;/p&gt;        &lt;input type=&quot;submit&quot; value=&quot;Submit&quot; /&gt;    &lt;/form&gt;    &#39;&#39;&#39;    return render_template_string(page)@app.route(&#39;/do_login&#39;, methods=[&#39;POST&#39;])def do_login():    name = request.form.get(&#39;user_name&#39;)    session[&#39;user_name&#39;] = name    return &#39;success&#39;@app.route(&#39;/show&#39;)def show():    return session[&#39;user_name&#39;]@app.route(&#39;/logout&#39;)def logout():    session.pop(&#39;user_name&#39;, None)    return redirect(url_for(&#39;login&#39;))if __name__ == &#39;__main__&#39;:    app.run()</code></pre><p><strong>代码的含义</strong></p><p>app.secret_key用于给session加密。</p><p>在/login中将向用户展示一个表单，要求输入一个名字，submit后将数据以post的方式传递给/do_login，/do_login将名字存放在session中。</p><p>如果用户成功登录，访问/show时会显示用户的名字。此时，打开调试工具，选择session面板，会看到有一个cookie的名称为session。</p><p>/logout用于登出，通过将session中的user_name字段pop即可。Flask中的session基于字典类型实现，调用pop方法时会返回pop的键对应的值；如果要pop的键并不存在，那么返回值是pop()的第二个参数。</p><p>另外，使用redirect()重定向时，一定要在前面加上return。</p><h1 id="%E8%AE%BE%E7%BD%AEsession%E7%9A%84%E6%9C%89%E6%95%88%E6%97%B6%E9%97%B4" tabindex="-1">设置session的有效时间</h1><p>设置session的有效时间设置为5分钟。</p><p>代码如下：</p><pre><code class="language-python">from datetime import timedeltafrom flask import session, appsession.permanent = Trueapp.permanent_session_lifetime = timedelta(minutes=5)</code></pre><h1 id="%E4%BD%BF%E7%94%A8cookie" tabindex="-1">使用Cookie</h1><p>Cookie是存储在客户端的记录访问者状态的数据。</p><p>常用的用于记录用户登录状态的session大多是基于cookie实现的。</p><p>cookie可以借助flask.Response来实现。</p><p>使用<code>Response.set_cookie</code>添加和删除cookie。</p><p><code>expires</code>参数用来设置cookie有效时间，值可以是<code>datetime</code>对象或者unix时间戳。</p><pre><code class="language-python">res.set_cookie(key=&#39;name&#39;, value=&#39;loen&#39;, expires=time.time()+6*60)</code></pre><p>上面的expire参数的值表示cookie在从现在开始的6分钟内都是有效的。</p><p>要删除cookie，将expire参数的值设为0即可：</p><pre><code class="language-python">res.set_cookie(&#39;name&#39;, &#39;&#39;, expires=0)</code></pre><p>详细的<code>app.py</code>代码如下：</p><pre><code class="language-python">import timefrom flask import Flask, request, Responseapp = Flask(__name__)@app.route(&#39;/add&#39;)def login():    res = Response(&#39;add cookies&#39;)    res.set_cookie(key=&#39;name&#39;, value=&#39;loen&#39;, expires=time.time() + 6 * 60)    return res@app.route(&#39;/show&#39;)def show():    return request.cookies.__str__()@app.route(&#39;/del&#39;)def del_cookie():    res = Response(&#39;delete cookies&#39;)    res.set_cookie(&#39;name&#39;, &#39;&#39;, expires=0)    return resif __name__ == &#39;__main__&#39;:    app.run()</code></pre><h1 id="%E9%97%AA%E5%AD%98%E7%B3%BB%E7%BB%9F-flashing-system" tabindex="-1">闪存系统 flashing system</h1><p>Flask 的闪存系统（flashing system）用于向用户提供反馈信息，这些反馈信息一般是对用户上一次操作的反馈。</p><p>反馈信息是存储在服务器端的，当服务器向客户端返回反馈信息后，<strong>这些反馈信息会被服务器端删除。</strong></p><p>详细的<code>app.py</code>代码如下：</p><pre><code class="language-python">import timefrom flask import Flask, get_flashed_messages, flashapp = Flask(__name__)app.secret_key = &#39;some_secret&#39;@app.route(&#39;/&#39;)def index():    return &#39;Hello index&#39;@app.route(&#39;/gen&#39;)def gen():    info = &#39;access at &#39; + time.time().__str__()    flash(info)    return info@app.route(&#39;/show1&#39;)def show1():    return get_flashed_messages().__str__()@app.route(&#39;/show2&#39;)def show2():    return get_flashed_messages().__str__()if __name__ == &#39;__main__&#39;:    app.run()</code></pre>]]>
                    </description>
                    <pubDate>Wed, 15 Feb 2023 17:58:04 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[凡人神将传游戏攻略]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/fan-ren-shen-jiang-chuan-you-xi-gong-lve</link>
                    <description>
                            <![CDATA[<h1 id="%E5%90%88%E6%9C%8D%E6%B4%BB%E5%8A%A8" tabindex="-1">合服活动</h1><p><img src="https://img.huangge1199.cn/blog/ftzsgl/2023-02-11-17-48-35-image.png" alt="" /><br />注：普通召唤券往后4000给100，5000给100，6000给150，7000给150，至此封顶</p><p><img src="https://img.huangge1199.cn/blog/ftzsgl/2023-02-11-17-49-07-image.png" alt="" /></p><p><img src="https://img.huangge1199.cn/blog/ftzsgl/2023-03-05-21-38-21-image.png" alt="" /><br />注：混沌玉：洪荒玉 = 1：50，使用混沌玉得到达2阶，即花销1000洪荒玉（100、150、200、250、300）后才能使用</p><h1 id="%E9%9B%B6%E6%B0%AA%E9%BB%84%E9%81%93%E7%A5%9E%E6%AD%A6" tabindex="-1">零氪黄道神武</h1><p><img src="https://img.huangge1199.cn/blog/ftzsgl/2023-06-26-10-00-27-image.jpg" alt="" /></p><h1 id="%E6%91%98%E6%98%9F%E6%A5%BC%E3%80%81%E7%A7%98%E5%A2%83%E3%80%81%E9%BE%99%E7%8F%A0%E7%A5%9E%E5%B0%86%E5%87%BA%E7%8E%B0%E9%A1%BA%E5%BA%8F" tabindex="-1">摘星楼、秘境、龙珠神将出现顺序</h1><ul><li>摘星楼：2金灵圣女、4火神祝融、6水神共工、8敖烈、10万圣公主、12北财神赵公明、14地神后土、16龙吉公主</li><li>秘境：1吕玲绮、5孙尚香、9马超、13大乔</li><li>龙珠：3蔡文姬、7小乔、11司马懿、15黄月英</li></ul><h1 id="%E7%82%BC%E5%A6%96%E5%98%89%E5%B9%B4%E5%8D%8E" tabindex="-1">炼妖嘉年华</h1><p>九星秘宝<br />500，500，1000，1500，1500，5000，7500，后面还有两个不知道</p><h1 id="%E6%8A%BD%E5%8D%A1%E5%98%89%E5%B9%B4%E5%8D%8E" tabindex="-1">抽卡嘉年华</h1><p>九星秘宝<br />500，500，1000，1500,，1500，5000，7500，后面还有两个不知道</p><h1 id="%E5%BD%92%E5%A2%9F" tabindex="-1">归墟</h1><p><img src="https://img.huangge1199.cn/blog/ftzsgl/2023-06-27-23-11-15-image.jpg" alt="" /></p><h1 id="%E8%B4%A2%E7%A5%9E%E5%AE%9D%E8%BD%AE" tabindex="-1">财神宝轮</h1><p>根据以往经验来看，到一定次数必出对应东西，具体如下：</p><ul><li>40 ：一万玉</li><li>80 ：百分之2</li><li>250：1星龙装</li><li>290：百分之2</li><li>370：2星龙装或福运至宝</li><li>390：5万玉</li><li>410：百分之5</li><li>690：3星龙装<br />推荐是到410次抽一回</li></ul>]]>
                    </description>
                    <pubDate>Sat, 11 Feb 2023 19:23:23 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[docker镜像构建以及宿主机和容器间的相互拷贝]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/docker镜像构建以及宿主机和容器间的相互拷贝</link>
                    <description>
                            <![CDATA[<h1 id="前言">前言</h1><p>主要学习docker的相关操作，构建镜像、docker容器运行、从容器内往外拷贝文件，向容器内拷贝文件，进入容器</p><h1 id="docker构建镜像">docker构建镜像</h1><p>编写Dockerfile文件：</p><pre><code class="language-shell">vi Dockerfile</code></pre><p>文件内输入</p><pre><code class="language-shell">from nginx</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-15-47-05-image.png" alt="" /></p><p>在同目录执行构建命令：</p><pre><code class="language-shell">docker build -t my-nginx .</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-15-48-33-image.png" alt="" /></p><h1 id="docker容器运行">docker容器运行</h1><p>执行命令：</p><pre><code class="language-shell"># 运行命令docker run --name my-nginx -d -p 40080:80 my-nginx# 查看所有容器信息docker ps -a</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-15-54-23-image.png" alt="" /></p><p>浏览器输入<code>IP:40080</code>，显示默认nginx页面</p><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-02-20-image.png" alt="" /></p><h1 id="从容器内往外拷贝文件">从容器内往外拷贝文件</h1><p>执行命令：</p><pre><code class="language-shell"># 拷贝文件docker cp my-nginx:/usr/share/nginx/html/index.html index.html# 查看文件内容cat index.html# 修改文件内容vi index.html# 查看文件内容cat index.html</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-06-03-image.png" alt="" /></p><h1 id="向容器内拷贝文件">向容器内拷贝文件</h1><p>执行命令：</p><pre><code class="language-shell"># 拷贝文件docker cp index.html my-nginx:/usr/share/nginx/html/index.html </code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-08-22-image.png" alt="" /></p><p>浏览器输入<code>IP:40080</code>，显示页面已经改变</p><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-09-59-image.png" alt="" /></p><h1 id="进入容器">进入容器</h1><p>为了方便查看变化，这里拷贝了一份不一样的文件进人容器，执行命令：</p><pre><code class="language-shell"># 修改文件名mv index.html new.html# 修改文件内容vi new.html# 拷贝文件进容器docker cp new.html my-nginx:/usr/share/nginx/html/new.html# 查看修改文件的内容cat new.html</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-34-23-image.png" alt="" /></p><p>执行命令：</p><pre><code class="language-shell"># 从容器中拷贝nginx配置文件docker cp my-nginx:/etc/nginx/conf.d/default.conf .# 查看配置文件cat default.conf</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-29-28-image.png" alt="" /></p><pre><code class="language-shell"># 修改配置文件vi default.conf# 查看修改后的配置文件cat default.conf</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-30-01-image.png" alt="" /></p><pre><code class="language-shell"># 再将配置文件拷贝回容器docker cp default.conf my-nginx:/etc/nginx/conf.d/default.conf# 进入容器docker exec -it my-nginx /bin/bash# 查看拷贝进容器的文件cat /usr/share/nginx/html/new.html</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-37-58-image.png" alt="" /></p><pre><code class="language-shell"># 查看拷贝进容器的nginx配置文件cat /etc/nginx/conf.d/default.conf# 重启nginxnginx -s reload# 退出容器exit</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-38-52-image.png" alt="" /></p><p>浏览器输入<code>IP:40080</code>，显示页面已经改变</p><p><img src="https://img.huangge1199.cn/blog/dockerBuilder/2023-02-08-16-39-30-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Wed, 08 Feb 2023 15:36:29 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[免费HTTPS证书部署]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/免费https证书部署</link>
                    <description>
                            <![CDATA[<h1 id="前言">前言</h1><p>由于腾讯云限制了免费证书的使用个数，而我之前因为免费就随意了很多，现在，一个正在使用的证书过期了，没法继续使用，这样就导致了在浏览器中不能一步到位的打开网站</p><h1 id="网站介绍">网站介绍</h1><p>使用的是<a href="https://freessl.cn/">FreeSSL.cn</a>网站，该网站提供免费的HTTPS证书申请，下面是网站首页</p><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-15-29-10-image.png" alt="" /></p><h1 id="安装acmesh">安装acme.sh</h1><p>我们需要先在服务器上安装acme.sh，建议使用root用户安装</p><pre><code class="language-shell">curl https://get.acme.sh | sh -s email=my@example.com</code></pre><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-15-49-45-image.png" alt="" /></p><h1 id="acme-域名配置">ACME 域名配置</h1><p>在首页中输入想要申请证书的域名，点击后面的按钮</p><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-15-31-54-image.png" alt="" /></p><p>点击下一步</p><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-15-32-57-image.png" alt="" /></p><p>根据内容，去你的域名管理处添加信息，添加后回来点击按钮</p><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-15-38-46-image.png" alt="" /></p><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-15-40-47-image.png" alt="" /></p><p>出现下面的页面，可以先直接点击完成</p><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-16-02-28-image.png" alt="" /></p><h1 id="部署证书">部署证书</h1><p>acme.sh 部署命令，这个就是上面图中的内容</p><pre><code class="language-shell">acme.sh --issue -d blog.huangge1199.cn  --dns dns_dp --server [专属 ACME 地址]</code></pre><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-16-07-46-image.png" alt="" /></p><p>生成证书，注意生成证书的路径根据自己的情况修改</p><pre><code class="language-shell">acme.sh --install-cert -d blog.huangge1199.cn \--key-file       /www/server/panel/vhost/cert/blog.huangge1199.cn/key.pem  \--fullchain-file /www/server/panel/vhost/cert/blog.huangge1199.cn/cert.pem \--reloadcmd     &quot;service nginx reload&quot;</code></pre><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-16-09-45-image.png" alt="" /></p><p>修改nginx配置文件，添加如下的内容，证书文件的路径和名字同生成证书的路径与名字一致</p><pre><code class="language-yml">ssl_certificate    /www/server/panel/vhost/cert/blog.huangge1199.cn/cert.pem;ssl_certificate_key    /www/server/panel/vhost/cert/blog.huangge1199.cn/key.pem;ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;ssl_prefer_server_ciphers on;</code></pre><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-16-12-23-image.png" alt="" /></p><p>nginx最好再重启一次</p><pre><code class="language-shell">service nginx reload</code></pre><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-16-13-25-image.png" alt="" /></p><h1 id="验证">验证</h1><p>点击跳转，<a href="[https://blog.huangge1199.cn/](https://blog.huangge1199.cn/)">龙儿之家</a></p><p><img src="https://img.huangge1199.cn/blog/useFreeSSL/2023-02-07-16-15-44-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Tue, 07 Feb 2023 15:15:03 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[群晖安装PostgreSQL]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/qun-hui-an-zhuang-postgresql</link>
                    <description>
                            <![CDATA[<h1 id="%E7%A1%AE%E8%AE%A4%E5%A5%97%E4%BB%B6%E4%B8%AD%E5%BF%83%E6%9C%89postgresql" tabindex="-1">确认套件中心有PostgreSQL</h1><p>我这边在套件中心中搜索到PostgreSQL了，要安装就先要确认有它，我这边的环境的spk7d 系统版本</p><p><img src="https://img.huangge1199.cn/blog/inPostgreSqlBySynology/2023-01-15-15-20-42-image.png" alt="" /></p><p>我在套件中心设置的套件来源有2个</p><ul><li><p><a href="https://packages.synocommunity.com/" target="_blank">https://packages.synocommunity.com/</a></p></li><li><p><a href="https://spk7.imnks.com/" target="_blank">https://spk7.imnks.com/</a></p></li></ul><p><img src="https://img.huangge1199.cn/blog/inPostgreSqlBySynology/2023-01-15-15-17-48-image.png" alt="" /></p><h1 id="%E5%AE%89%E8%A3%85" tabindex="-1">安装</h1><p>这步就简单了，直接在套件中心安装套件即可，安装过程中，需要设置用户名、密码和端口号，端口号不能重复的</p><p><img src="https://img.huangge1199.cn/blog/inPostgreSqlBySynology/2023-01-15-15-22-35-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sun, 15 Jan 2023 15:34:21 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[nvm安装nodejs]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/nvm-an-zhuang-nodejs</link>
                    <description>
                            <![CDATA[<h1 id="nvm%E4%B8%8B%E8%BD%BD" tabindex="-1">nvm下载</h1><p><a href="https://github.com/coreybutler/nvm-windows" target="_blank">nvm的GitHub下载地址</a></p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-22-23-07-image.png" alt="" /></p><p>进入后下载</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-22-24-26-image.png" alt="" /></p><h1 id="nvm%E5%AE%89%E8%A3%85" tabindex="-1">nvm安装</h1><p>下载后双击exe文件进行安装，同意后点击next</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-22-26-41-image.png" alt="" /></p><p>设置安装目录</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-22-28-55-image.png" alt="" /></p><p>设置nodejs目录，最好不要带空格</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-22-31-09-image.png" alt="" /></p><p>点击install安装</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-22-31-34-image.png" alt="" /></p><p>点击Finish安装完成</p><h1 id="nvm%E6%B7%BB%E5%8A%A0%E6%B7%98%E5%AE%9D%E9%95%9C%E5%83%8F" tabindex="-1">nvm添加淘宝镜像</h1><p>打开nvm安装目录下的settings.txt文件，添加淘宝镜像地址，红框内为新增的</p><pre><code class="language-yaml">node_mirror: https://npm.taobao.org/mirrors/node/npm_mirror: https://npm.taobao.org/mirrors/npm/</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-22-40-28-image.png" alt="" /></p><h1 id="nvm%E8%AE%BE%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F" tabindex="-1">nvm设置环境变量</h1><p>此电脑右键点击属性</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-22-47-21-image.png" alt="" /></p><p>点击高级系统设置</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-06-30-image.png" alt="" /></p><p>点击环境变量</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-08-12-image.png" alt="" /></p><p>确认环境变量中有NVM_HOME和NVM_SYMLINK</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-10-10-image.png" alt="" /></p><p>确认</p><p>管理员身份运行cmd，执行查看的命令确认安装成功</p><pre><code class="language-bash">nvm -v</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-16-26-image.png" alt="" /></p><h1 id="node%E5%AE%89%E8%A3%85" tabindex="-1">node安装</h1><p>执行命令列出有效可下载的node版本</p><pre><code class="language-bash">nvm list available</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-20-11-image.png" alt="" /></p><p>执行安装命令安装指定版本的node</p><pre><code class="language-bash">nvm install &lt;version&gt;</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-21-39-image.png" alt="" /></p><p>执行命令查看已安装的node版本</p><pre><code class="language-bash">nvm list</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-22-47-image.png" alt="" /></p><p>执行命令使用某个版本的node</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-25-48-image.png" alt="" /></p><p>执行命令查看node版本</p><pre><code class="language-bash">node -v</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-26-48-image.png" alt="" /></p><h1 id="node%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F" tabindex="-1">node环境变量</h1><p>新建目录node_global、node_cache，可以建在nodejs目录下</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-33-44-image.png" alt="" /></p><p>新建环境变量，变量名：NODE_PATH，变量值：node_global路径\node_modules</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-37-35-image.png" alt="" /></p><p>在Path环境变量中增加node_global路径</p><p><img src="https://img.huangge1199.cn/blog/inNodejsByNvm/2023-01-14-23-41-08-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sat, 14 Jan 2023 23:56:49 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[TDengine安装使用]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/tdengine安装使用</link>
                    <description>
                            <![CDATA[<h1 id="引言">引言</h1><p>近期，听说了时序数据库TDengine，本人的好奇心又出来了，同是时序数据库的InfluxDB不也挺好的嘛？通过一些网上的资料以及些简单的实际操作，本人得出的结论是：</p><ul><li><p>数据量少时，InfluxDB的性能好些</p></li><li><p>当数据量越来越大之后，TDengine就更适合你使用了</p></li></ul><h1 id="内容介绍">内容介绍</h1><p>本文将会围绕TGengine进行简单的介绍，当然我也是初次使用，这份文档也只是初步的学习记录，如果有朋友在实际中使用了TGengine并且觉得这篇文章有什么问题，还请在下方留言，我会根据实际情况对文章进行修改，这样也是为了防止给别人留坑</p><ol><li><p>对TGengine做下简单介绍（摘抄自官方文档）</p></li><li><p>安装TGengine服务端的过程</p></li><li><p>TDengine 数据建模</p></li><li><p>DataGrip如何查看数据</p></li><li><p>使用java语言进行REST连接测试</p></li></ol><p>另外，我这边服务端是使用<code>TDengine-server-2.6.0.30-Linux-x64.tar.gz</code>进行安装的</p><h1 id="介绍">介绍</h1><blockquote><p>注：本段内容摘自<a href="https://docs.taosdata.com/2.6/intro/">官方文档</a></p></blockquote><p>TDengine 是一款高性能、分布式、支持 SQL 的时序数据库 (Database)，其核心代码，包括集群功能全部开源（开源协议，AGPL v3.0）。TDengine 能被广泛运用于物联网、工业互联网、车联网、IT 运维、金融等领域。除核心的时序数据库 (Database) 功能外，TDengine 还提供缓存、数据订阅、流式计算等大数据平台所需要的系列功能，最大程度减少研发和运维的复杂度。</p><h1 id="牢骚">牢骚</h1><p>在对比的过程中，我发现TDengine的官方文档不是太好，怎么说尼，虽然更改方面都提及到了，但是需要看很多内容之后才能完全的解决好，这个就不是太好了。比如说安装，虽然在立即开始里面有，但是详细的安装卸载是放在运维指南里面，按照我们的习惯，从上往下，从左往右，可能看到最后，才看到安装和卸载，但是在这之前却是有大量的实际操作文档在中间夹杂。</p><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-15-26-47-image.png" alt="" /></p><h1 id="安装服务端">安装服务端</h1><p>目前 2.X 版服务端 taosd 和 taosAdapter 仅在 Linux 系统上安装和运行，应用驱动 taosc 与 TDengine CLI 可以在 Windows 或 Linux 上安装和运行。另外在 2.4 之前的版本中没有 taosAdapter，RESTful 接口是由 taosd 内置的 HTTP 服务提供的。</p><ol><li><p>下载安装包<font color='green'>TDengine-server-2.6.0.30-Linux-x64.tar.gz (45 M)</font>并上传至服务器</p><p>链接: <a href="https://pan.baidu.com/s/1-w7O2xUuq0iaF1glh36bow?pwd=ansm">https://pan.baidu.com/s/1-w7O2xUuq0iaF1glh36bow?pwd=ansm</a> 提取码: ansm</p></li><li><p>进入安装包所在目录，解压文件</p><pre><code class="language-shell"># 解压命令tar -zxvf TDengine-server-2.6.0.30-Linux-x64.tar.gz</code></pre></li><li><p>进入解压目录，执行其中的 install.sh 安装脚本</p><pre><code class="language-shell"># 进入解压目录命令（目录根据自己的解决自行更改）cd /app/TDengine-server-2.6.0.30# 执行安装命令./install.sh</code></pre></li></ol><blockquote><p>注：中途两次输入，直接回车就好，什么都不用输入</p></blockquote><p><img src="https://img.huangge1199.cn/blog/tgengine-1/42c99f658589ee0c93af7c4742ce9616684c4ef6.png" alt="2022-11-24-16-07-50-image.png" /></p><ol start="4"><li><p>启动taosd并确认状态</p><pre><code class="language-shell"># 启动命令systemctl start taosd# 确认状态systemctl status taosd</code></pre></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-16-16-30-image.png" alt="" /></p><ol start="5"><li><p>启动taosAdapter并确认状态</p><blockquote><p>注：TDengine 在 2.4 版本之后包含一个独立组件 taosAdapter 需要使用 systemctl 命令管理 taosAdapter 服务的启动和停止，不符合的要跳过本步骤</p></blockquote><pre><code class="language-shell">```shell# 启动命令systemctl start taosadapter# 确认状态systemctl status taosadapter</code></pre><pre><code></code></pre></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-16-23-43-image.png" alt="" /></p><ol start="6"><li><p>进入taos，确认安装成功、</p><pre><code class="language-shell"># 启动命令(默认密码taosdata)taos -p</code></pre></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-16-27-20-image.png" alt="" /></p><h1 id="tdengine-数据建模">TDengine 数据建模</h1><ol><li><p>创建数据库</p><pre><code class="language-shell"># 创建数据库命令CREATE DATABASE power;# 切换数据库USE power;</code></pre></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-16-33-10-image.png" alt="" /></p><ol start="2"><li><p>创建表</p><pre><code class="language-shell"># 创建表create table t (ts timestamp, speed int);# 插入2条数据(建议插入两条记录时隔几秒)insert into t values (now, 10);insert into t values (now, 20);</code></pre></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-16-37-22-image.png" alt="" /></p><ol start="3"><li><p>查询表数据</p><pre><code class="language-shell"># 查询表 tselect * from t;</code></pre></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-16-38-51-image.png" alt="" /></p><h1 id="datagrip查看数据">DataGrip查看数据</h1><ol><li><p>编译jar</p><p>从 GitHub 仓库克隆 JDBC 连接器的源码，<code>git clone https://github.com/taosdata/taos-connector-jdbc.git -b 2.0.40</code>（此处推荐 -b 指定发布了的 Tags 版本）</p><p>克隆完源码后，若是编译 2.0.40 及以下版本的將commons-logging 依赖包的 scope 值由 test 改为 compile</p><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-16-59-32-image.png" alt="" /></p><p>在目录下执行：<code>mvn clean package -D maven.test.skip=true</code></p></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-17-02-35-image.png" alt="" /></p><ol start="2"><li><p>自建驱动</p><p>使用Driver and Data Source，自建驱动，注意红框内容，jar包是之前编译生成的</p></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-16-48-41-image.png" alt="" /></p><ol start="3"><li><p>创建数据库连接</p><p>第一个红框Driver选择之前自建的，第二个红框URL 写<code>jdbc:TAOS-RS://IP:6041/数据库名</code></p></li></ol><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-17-08-57-image.png" alt="" /></p><h1 id="java进行rest连接测试">java进行REST连接测试</h1><p>新建Springboot项目，maven引入jar包</p><pre><code class="language-xml">&lt;dependency&gt;    &lt;groupId&gt;com.taosdata.jdbc&lt;/groupId&gt;    &lt;artifactId&gt;taos-jdbcdriver&lt;/artifactId&gt;    &lt;version&gt;2.0.40&lt;/version&gt;&lt;/dependency&gt;</code></pre><p>main 方法:</p><pre><code class="language-java">public static void main(String[] args) throws SQLException {    String jdbcUrl = &quot;jdbc:TAOS-RS://IP:6041/数据库名?user=用户名&amp;password=密码&quot;;    Connection conn = DriverManager.getConnection(jdbcUrl);    System.out.println(&quot;Connected&quot;);    conn.close();}</code></pre><p>测试结果：</p><p><img src="https://img.huangge1199.cn/blog/tgengine-1/2022-11-24-17-15-33-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Thu, 24 Nov 2022 15:03:40 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[群晖nas上部署gitea后修改IP地址]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/群晖nas上部署gitea后修改ip地址</link>
                    <description>
                            <![CDATA[<h1 id="事件">事件</h1><p>今天，我在nas的套件中心中发现了Gitea这个套件，想到自己的代码都是保存在GitHub或者Gitee上面的，<br />于是乎我边在nas上面装了这个套件，装备将代码在nas里面也备份一份</p><p>我的nas所在网络没有公网IP，用内网穿透形式弄的，但是在用穿透后的<code>IP:端口</code>进入时，就报了下面的警告</p><p><img src="https://img.huangge1199.cn/blog/giteaNas/2022-10-25-12-25-02-image.png" alt="" /></p><p>看介绍，是说地址不一样了，绿框中的地址分别是我本地地址和穿透后的公网地址，为了方便，我就想把地址换成<br />公网的地址，这样以后复制地址什么的也方便</p><h1 id="换ip">换IP</h1><p>有两种方法：</p><ol><li>每次都将本地IP改为穿透的公网ip</li><li>修改配置文件<code>conf.ini</code></li></ol><p>第一种方法需要每一次都改，太麻烦了，我这里使用的是第二种方法</p><p>群晖的gitea的配置文件是在安装目录下的<code>/var</code>下面，我安装在<code>/var/packages</code>里</p><p><img src="https://img.huangge1199.cn/blog/giteaNas/2022-10-25-12-24-05-1666671836575.jpg" alt="" /></p><p>打开<code>conf.ini</code>文件，注意这地方需要root权限，因此执行命令</p><pre><code class="language-shell">sudo vi conf.ini</code></pre><p><img src="https://img.huangge1199.cn/blog/giteaNas/2022-10-25-12-31-55-image.png" alt="" /></p><p>将12行这地方改成穿透后的公网IP</p><h1 id="重启gitea套件">重启gitea套件</h1><ol><li>在套件中心中找到gitea，然后停用</li></ol><p><img src="https://img.huangge1199.cn/blog/giteaNas/2022-10-25-12-38-13-image.png" alt="" /></p><ol start="2"><li>启动gitea</li></ol><p><img src="https://img.huangge1199.cn/blog/giteaNas/2022-10-25-12-39-15-image.png" alt="" /></p><h1 id="完成验证">完成验证</h1><p>为了确保成功，完成后再通过穿透后的公网进入，页面的红框消失</p>]]>
                    </description>
                    <pubDate>Tue, 25 Oct 2022 11:14:24 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[vue下el-popover组件实现滚轴跟随功能]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/vue下el-popover组件实现滚轴跟随功能</link>
                    <description>
                            <![CDATA[<h1 id="描述">描述</h1><p>使用的是点击触发弹出内容，目标是在弹出内容的情况下，上下来回滚动鼠标，弹出内容和点击按钮不分离</p><p>通过监听页面滚动来实现功能，当监听到页面有滚动时，通过组件的updatePopper()方法来更新组件的位置</p><h1 id="代码">代码</h1><pre><code class="language-vue">&lt;el-popover     ref=&quot;popover&quot;    placement=&quot;right&quot;    width=&quot;400&quot;    trigger=&quot;click&quot;    style=&quot;position: relative&quot;&gt;    &lt;el-table :data=&quot;gridData&quot;&gt;      &lt;el-table-column width=&quot;150&quot; property=&quot;date&quot; label=&quot;日期&quot;&gt;&lt;/el-table-column&gt;      &lt;el-table-column width=&quot;100&quot; property=&quot;name&quot; label=&quot;姓名&quot;&gt;&lt;/el-table-column&gt;      &lt;el-table-column width=&quot;300&quot; property=&quot;address&quot; label=&quot;地址&quot;&gt;&lt;/el-table-column&gt;    &lt;/el-table&gt;    &lt;el-button slot=&quot;reference&quot;&gt;click 激活&lt;/el-button&gt;&lt;/el-popover&gt;&lt;script&gt;export default {  data() {    return {      gridData: [{        date: '2016-05-02',        name: '王小虎',        address: '上海市普陀区金沙江路 1518 弄'      }, {        date: '2016-05-04',        name: '王小虎',        address: '上海市普陀区金沙江路 1518 弄'      }, {        date: '2016-05-01',        name: '王小虎',        address: '上海市普陀区金沙江路 1518 弄'      }, {        date: '2016-05-03',        name: '王小虎',        address: '上海市普陀区金沙江路 1518 弄'      }]    };  },  mounted() {    window.addEventListener('scroll', this.handleScroll, true)  },  methods: {    handleScroll() {      this.$refs.popover.updatePopper()    }  }};&lt;/script&gt;</code></pre>]]>
                    </description>
                    <pubDate>Wed, 19 Oct 2022 14:13:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2415. 反转二叉树的奇数层]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2415反转二叉树的奇数层</link>
                    <description>
                            <![CDATA[<p>311周赛第三题</p><p>原题链接：<a href="https://leetcode.cn/problems/reverse-odd-levels-of-binary-tree/">2415. 反转二叉树的奇数层</a></p><h1 id="题目">题目</h1><p>给你一棵 <strong>完美</strong> 二叉树的根节点 <code>root</code> ，请你反转这棵树中每个 <strong>奇数</strong> 层的节点值。</p><ul>  <li>例如，假设第 3 层的节点值是 <code>[2,1,3,4,7,11,29,18]</code> ，那么反转后它应该变成 <code>[18,29,11,7,4,3,1,2]</code> 。</li> </ul><p>反转后，返回树的根节点。</p><p><strong>完美</strong> 二叉树需满足：二叉树的所有父节点都有两个子节点，且所有叶子节点都在同一层。</p><p>节点的 <strong>层数</strong> 等于该节点到根节点之间的边数。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p> <img alt="" src="https://assets.leetcode.com/uploads/2022/07/28/first_case1.png" style="width: 626px; height: 191px;" /> <pre><strong>输入：</strong>root = [2,3,5,8,13,21,34]<strong>输出：</strong>[2,5,3,8,13,21,34]<strong>解释：</strong>这棵树只有一个奇数层。在第 1 层的节点分别是 3、5 ，反转后为 5、3 。</pre><p><strong>示例 2：</strong></p> <img alt="" src="https://assets.leetcode.com/uploads/2022/07/28/second_case3.png" style="width: 591px; height: 111px;" /> <pre><strong>输入：</strong>root = [7,13,11]<strong>输出：</strong>[7,11,13]<strong>解释：</strong> 在第 1 层的节点分别是 13、11 ，反转后为 11、13 。 </pre><p><strong>示例 3：</strong></p><pre><strong>输入：</strong>root = [0,1,2,0,0,0,0,1,1,1,1,2,2,2,2]<strong>输出：</strong>[0,2,1,0,0,0,0,2,2,2,2,1,1,1,1]<strong>解释：</strong>奇数层由非零值组成。在第 1 层的节点分别是 1、2 ，反转后为 2、1 。在第 3 层的节点分别是 1、1、1、1、2、2、2、2 ，反转后为 2、2、2、2、1、1、1、1 。</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul>  <li>树中的节点数目在范围 <code>[1, 2<sup>14</sup>]</code> 内</li>  <li><code>0 &lt;= Node.val &lt;= 10<sup>5</sup></code></li>  <li><code>root</code> 是一棵 <strong>完美</strong> 二叉树</li> </ul><h1 id="思路">思路：</h1><blockquote><p>看了灵神的周赛视频讲解，或多或少有影响</p></blockquote><p>这题有两种方法，都可以做交换值：</p><ul><li>BFS</li><li>DFS</li></ul><h1 id="bfs代码">BFS代码</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">import java.util.*;class Solution {    public TreeNode reverseOddLevels(TreeNode root) {        /*        如果是空节点直接返回         */        if (root == null) {            return null;        }        // 队列存入每层的节点        Queue&lt;TreeNode&gt; queue = new LinkedList&lt;&gt;();        queue.add(root);        int level = 0;        while (!queue.isEmpty()) {            /*            拿出每层的节点放入列表中，并将下一层的节点放入队列中             */            int size = queue.size();            List&lt;TreeNode&gt; nodeList = new ArrayList&lt;&gt;();            for (int i = 0; i &lt; size; i++) {                TreeNode node = queue.poll();                nodeList.add(node);                if (node.left != null) {                    queue.add(node.left);                    queue.add(node.right);                }            }            /*            奇数层，在列表中交换收尾节点的值             */            if (level == 1) {                int nodeSize = nodeList.size();                for (int i = 0; i &lt; nodeSize / 2; i++) {                    int num = nodeList.get(i).val;                    nodeList.get(i).val = nodeList.get(nodeSize - i - 1).val;                    nodeList.get(nodeSize - i - 1).val = num;                }            }            // 改变奇偶层            level = 1 - level;        }        return root;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from typing import Optionalclass Solution:    def reverseOddLevels(self, root: Optional[TreeNode]) -&gt; Optional[TreeNode]:        queue = [root]        level = 1        while queue[0].left:            next = []            for node in queue:                next += [node.left, node.right]            queue = next            if level:                for i in range(len(queue) // 2):                    node1, node2 = queue[i], queue[len(queue) - 1 - i]                    node1.val, node2.val = node2.val, node1.val            level = 1 - level        return root</code></pre><!-- endtab --><p>{% endtabs %}</p><h1 id="dfs代码">DFS代码</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">import java.util.*;class Solution {    public TreeNode reverseOddLevels(TreeNode root) {        if (root == null) {            return root;        }        dfs(root.left, root.right, 1);        return root;    }        private void dfs(TreeNode left, TreeNode right, int level) {        if (left == null) {            return;        }        if (level == 1) {            // 如果是奇数层，交换值            int tmp = left.val;            left.val = right.val;            right.val = tmp;        }        dfs(left.left, right.right, 1 - level);        dfs(left.right, right.left, 1 - level);    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from typing import Optionalclass Solution:    def reverseOddLevels(self, root: Optional[TreeNode]) -&gt; Optional[TreeNode]:        def dfs(left, right, level: bool) -&gt; None:            if left is None: return            if level: left.val, right.val = right.val, left.val            dfs(left.left, right.right, not level)            dfs(left.right, right.left, not level)        dfs(root.left, root.right, True)        return root</code></pre><!-- endtab --> <p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Tue, 20 Sep 2022 00:28:22 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2414:最长的字母序连续子字符串的长度]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2414最长的字母序连续子字符串的长度</link>
                    <description>
                            <![CDATA[<p>311周赛第二题</p><p>原题链接：<a href="https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring/">2414. 最长的字母序连续子字符串的长度</a></p><h1 id="题目">题目</h1><p><strong>字母序连续字符串</strong> 是由字母表中连续字母组成的字符串。换句话说，字符串 <code>"abcdefghijklmnopqrstuvwxyz"</code> 的任意子字符串都是 <strong>字母序连续字符串</strong> 。</p><ul>  <li>例如，<code>"abc"</code> 是一个字母序连续字符串，而 <code>"acb"</code> 和 <code>"za"</code> 不是。</li> </ul><p>给你一个仅由小写英文字母组成的字符串 <code>s</code> ，返回其 <strong>最长</strong> 的 字母序连续子字符串 的长度。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>s = "abacaba"<strong>输出：</strong>2<strong>解释：</strong>共有 4 个不同的字母序连续子字符串 "a"、"b"、"c" 和 "ab" 。"ab" 是最长的字母序连续子字符串。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>s = "abcde"<strong>输出：</strong>5<strong>解释：</strong>"abcde" 是最长的字母序连续子字符串。</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul>  <li><code>1 &lt;= s.length &lt;= 10<sup>5</sup></code></li>  <li><code>s</code> 由小写英文字母组成</li> </ul><h1 id="个人解法">个人解法</h1><p>遍历一次，判断相邻字符是否连续，找到最长的连续子字符串的长度</p><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public int longestContinuousSubstring(String s) {        int cnt = 0;        int bf = 0;        for (int i = 1; i &lt; s.length(); i++) {            if (s.charAt(i) - s.charAt(i - 1) != 1) {                cnt = Math.max(cnt, i - bf);                bf = i;            }        }        return Math.max(cnt, s.length() - bf);    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">class Solution:    def longestContinuousSubstring(self, s: str) -&gt; int:        cnt = bf = 0        for i in range(1, len(s)):            if ord(s[i]) - ord(s[i - 1]) != 1:                cnt = max(cnt, i - bf)                bf = i        return max(cnt, len(s) - bf)</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Mon, 19 Sep 2022 22:47:57 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2413:最小偶倍数]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2413最小偶倍数</link>
                    <description>
                            <![CDATA[<p>311周赛第一题</p><p>原题链接：<a href="https://leetcode.cn/problems/smallest-even-multiple/">2413. 最小偶倍数</a></p><h1 id="题目">题目</h1><p>给你一个正整数 <code>n</code> ，返回 <code>2</code><em> </em>和<em> </em><code>n</code> 的最小公倍数（正整数）。</p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>n = 5<strong>输出：</strong>10<strong>解释：</strong>5 和 2 的最小公倍数是 10 。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>n = 6<strong>输出：</strong>6<strong>解释：</strong>6 和 2 的最小公倍数是 6 。注意数字会是它自身的倍数。</pre><p><strong>提示：</strong></p><ul>  <li><code>1 &lt;= n &lt;= 150</code></li> </ul><h1 id="个人解法">个人解法</h1><p>这题比较简单，就直接上代码</p><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public int smallestEvenMultiple(int n) {        return n % 2 == 0 ? n : n * 2;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">class Solution:    def smallestEvenMultiple(self, n: int) -&gt; int:        return n if n % 2 == 0 else n * 2</code></pre><!-- endtab --><!-- tab Python3 使用lcm --><pre><code class="language-python">from math import lcm    class Solution:    def smallestEvenMultiple(self, n: int) -&gt; int:        return lcm(n, 2)</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Mon, 19 Sep 2022 21:47:05 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[python3学习笔记--pairwise]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/python3学习笔记--pairwise</link>
                    <description>
                            <![CDATA[<h1 id="说明">说明</h1><p>pairwise(iterable)是itertools下的一个方法<br />该方法是会返回传入列表所有相邻元素，如果传入的数据少于两个，会返回空</p><h1 id="官方文档">官方文档</h1><p>Return successive overlapping pairs taken from the input iterable.</p><p>The number of 2-tuples in the output iterator will be one fewer than the number of inputs. It will be empty if the input iterable has fewer than two values.</p><p>Roughly equivalent to:</p><pre><code class="language-python">def pairwise(iterable):    # pairwise('ABCDEFG') --&gt; AB BC CD DE EF FG    a, b = tee(iterable)    next(b, None)    return zip(a, b)</code></pre><h1 id="源码">源码</h1><p>在<code>itertools.py</code>文件中</p><pre><code class="language-python">class pairwise(object):    &quot;&quot;&quot;    Return an iterator of overlapping pairs taken from the input iterator.            s -&gt; (s0,s1), (s1,s2), (s2, s3), ...    &quot;&quot;&quot;    def __getattribute__(self, *args, **kwargs): # real signature unknown        &quot;&quot;&quot; Return getattr(self, name). &quot;&quot;&quot;        pass    def __init__(self, *args, **kwargs): # real signature unknown        pass    def __iter__(self, *args, **kwargs): # real signature unknown        &quot;&quot;&quot; Implement iter(self). &quot;&quot;&quot;        pass    @staticmethod # known case of __new__    def __new__(*args, **kwargs): # real signature unknown        &quot;&quot;&quot; Create and return a new object.  See help(type) for accurate signature. &quot;&quot;&quot;        pass    def __next__(self, *args, **kwargs): # real signature unknown        &quot;&quot;&quot; Implement next(self). &quot;&quot;&quot;        pass</code></pre><h1 id="参考代码">参考代码</h1><p>代码：</p><pre><code class="language-python">#!/usr/bin/env python3# @Time : 2022/9/5 20:23# @Author : 轩辕龙儿# @File : pyPairwise.py # @Software: PyCharmfrom itertools import pairwiseif __name__ == &quot;__main__&quot;:    arrs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]    print(&quot;传入数据：0, 1, 2, 3, 4, 5, 6, 7, 8, 9&quot;)    for arr in pairwise(arrs):        print(str(arr[0]) + &quot;,&quot; + str(arr[1]))    print(&quot;------------------------------------&quot;)    print(&quot;传入数据：1&quot;)    for arr in pairwise([1]):        print(str(arr[0]) + &quot;,&quot; + str(arr[1]))</code></pre><p>控制台输出：</p><pre><code>&quot;D:\Program Files\Python310\python.exe&quot; D:/project/leet-code-python/study/pyPairwise.py 传入数据：0, 1, 2, 3, 4, 5, 6, 7, 8, 90,11,22,33,44,55,66,77,88,9------------------------------------传入数据：1Process finished with exit code 0</code></pre>]]>
                    </description>
                    <pubDate>Mon, 05 Sep 2022 20:52:18 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[历史上的今天--8月17日]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/历史上的今天--8月17日</link>
                    <description>
                            <![CDATA[<p>2016年8月17日</p><ul><li>里约奥运会中国女乒团体夺金</li><li>里约奥运会曹缘获男子单人3米板冠军</li></ul><p>2015年8月17日</p><ul><li>泰国曼谷炸弹袭击事件</li></ul><p>2008年8月17日</p><ul><li>美国游泳神童菲尔普斯在北京奥运会创造神话</li><li>南部非洲发展共同体（南共体）自由贸易区正式启动</li></ul><p>2005年8月17日</p><ul><li>胡锦涛与肯尼亚总统齐贝吉会谈</li></ul><p>2000年8月17日</p><ul><li>三峡库区首批移民抵上海</li></ul><p>1999年8月17日</p><ul><li>土耳其发生强烈地震 1.8万人丧生</li></ul><p>1998年8月17日</p><ul><li>海灯法师名誉案</li><li>克林顿承认和莱温斯基有不正当关系</li></ul><p>1996年8月17日</p><ul><li>俄发射“联盟ＴＭ-24”号宇宙飞船</li><li>日本成功发射两颗卫星</li></ul><p>1993年8月17日</p><ul><li>数学家冯康逝世</li></ul><p>1992年8月17日</p><ul><li>清理三角债基本结束</li><li>南部非洲发展共同体成立</li></ul><p>1990年8月17日</p><ul><li>伊拉克从伊朗撤军并释放战俘</li><li>我国建成第一台天文子午环</li></ul><p>1988年8月17日</p><ul><li>巴基斯坦总统齐亚在飞行爆炸中死亡</li></ul><p>1987年8月17日</p><ul><li>德国纳粹党副领袖赫斯死亡</li></ul><p>1982年8月17日</p><ul><li>世界上第一张镭射唱片(CD)的诞生</li><li>中美发表《八一七公报》</li></ul><p>1971年8月17日</p><ul><li>纳粹德国陆军元帅威廉·李斯特病逝</li></ul><p>1969年8月17日</p><ul><li>世界上规模最大的嬉皮士聚会</li></ul><p>1968年8月17日</p><ul><li>尼日利亚内战导致饥荒灾难</li></ul><p>1964年8月17日</p><ul><li>我国试办托拉斯</li></ul><p>1958年8月17日</p><ul><li>北戴河会议掀起全民大炼钢铁运动</li><li>中央通过《关于在农村建立人民公社的决议》</li></ul><p>1952年8月17日</p><ul><li>中国围棋“棋圣”聂卫平出生</li><li>周恩来总理访问苏联</li></ul><p>1949年8月17日</p><ul><li>解放军攻占福州</li><li>日本松川事件发生</li></ul><p>1945年8月17日</p><ul><li>溥仪被苏军俘获</li><li>印度尼西亚八月革命爆发</li></ul><p>1937年8月17日</p><ul><li>中国著名书法家刘炳森出生</li><li>阎海文殉国</li></ul><p>1931年8月17日</p><ul><li>邓演达被逮捕</li></ul><p>1926年8月17日</p><ul><li>中国第三代领导核心江泽民主席诞辰</li></ul><p>1895年8月17日</p><ul><li>《中外纪闻》创刊</li></ul><p>1893年8月17日</p><ul><li>民间音乐家 “瞎子阿炳”华彦钧出生</li></ul><p>1877年8月17日</p><ul><li>左宗棠奏请在新疆设行省</li></ul><p>1850年8月17日</p><ul><li>南美独立战争领袖圣马丁逝世</li></ul><p>1807年8月17日</p><ul><li>轮船在全世界第一次投入商业使用</li></ul><p>1786年8月17日</p><ul><li>普鲁士国王腓特烈大帝逝世</li></ul><p>1740年8月17日</p><ul><li>本笃十四世当选为教皇</li></ul><p>1648年8月17日</p><ul><li>普雷斯顿战役爆</li></ul><p>1601年8月17日</p><ul><li>法国数学家费马出生</li></ul><p>1307年8月17日</p><ul><li>孔子被加封为 “大成至圣文宣王”</li></ul>]]>
                    </description>
                    <pubDate>Wed, 17 Aug 2022 10:45:26 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[windows server下安装zookeeper和kafka集群]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/windowsserver下安装zookeeper和kafka集群</link>
                    <description>
                            <![CDATA[<h1 id="安装说明">安装说明</h1><p>单机部署zookeeper和kafka集群，kafka使用2.8.0版本的，该版本已经将zookeeper集成在内了，因此只需要下载kafka的包即可。</p><p>安装目录：C:/kafka/</p><p>三个节点都在目录下，依次为kafka1、kafka2、kafka3</p><h1 id="下载">下载</h1><p>从kafka官网下载：<a href="https://archive.apache.org/dist/kafka/2.8.0/kafka_2.13-2.8.0.tgz">kafka_2.13-2.8.0.tgz 下载地址</a></p><p>下载好后，将内容解压后，依次拷贝到kafka1、kafka2、kafka3的目录下，作为集群的3个节点</p><h1 id="zookeeper">zookeeper</h1><h2 id="1配置文件">1、配置文件</h2><p>修改zookeeper的配置文件，conf/zookeeper.properties，以节点1为例，确保有以下内容：</p><pre><code class="language-properties">dataDir=C:/kafka/kafka1/zkDatadataLogDir=C:/kafka/kafka1/zkLogclientPort=2187tickTime=2000initLimit=10syncLimit=5server.1=192.168.0.116:2887:3887server.2=192.168.0.116:2888:3888server.3=192.168.0.116:2889:3889</code></pre><p>三个节点的clientPort依次设置成2187、2188、2189</p><blockquote><p>注意：</p><ul><li><p>三个节点的dataDir和dataLogDir的目录不同</p></li><li><p>dataDir和dataLogDir必须保证目录存在，不会根据配置文件自动生成</p></li><li><p>余下内容全部相同</p></li></ul></blockquote><h2 id="2创建myid文件">2、创建myid文件</h2><p>依次在三个节点的dataDir目录下创建myid文件，内容依次填入1、2、3。</p><h2 id="3创建启动脚本">3、创建启动脚本</h2><p>依次在三个节点kafka的目录下添加启动脚本zkStart.bat</p><pre><code class="language-batch">.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties</code></pre><h1 id="kafka">kafka</h1><h2 id="1修改配置文件">1、修改配置文件</h2><p>修改kafka的配置文件，server.properties，以节点1为例，修改内容如下：</p><pre><code class="language-properties">broker.id=0listeners=PLAINTEXT://192.168.0.116:9097advertised.listeners=PLAINTEXT://192.168.0.116:9097host.name= 192.168.0.116port=9097log.dirs=C:/kafka/kafka1/logzookeeper.connect=192.168.0.116:2187,192.168.0.116:2188,192.168.0.116:2189</code></pre><blockquote><p>注意：</p><ul><li><p>broker.id：依次为0、1、2</p></li><li><p>port：依次为9097、9098、9099</p></li><li><p>log.dirs必须保证目录存在，不会根据配置文件自动生成</p></li></ul></blockquote><h2 id="2创建启动停止脚本">2、创建启动、停止脚本</h2><p>依次在三个节点kafka的目录下添加</p><p>启动脚本start.bat：</p><pre><code class="language-batch">./bin/kafka-server-start.sh config/server.properties</code></pre><p>停止脚本stop.bat：</p><pre><code class="language-batch">./bin/kafka-server-stop.sh config/server.properties</code></pre><h1 id="测试发送消息">测试：发送消息</h1><h2 id="1创建主题">1、创建主题</h2><p>在kafka目录下执行下面的命令</p><pre><code class="language-batch">kafka-topics.bat --create --zookeeper IP:2181 --replication-factor 3 --partitions 1 --topic test7</code></pre><h2 id="2创建生产者">2、创建生产者</h2><p>在kafka的bin\windows目录下执行下面的命令</p><pre><code class="language-batch">kafka-console-producer.bat --broker-list 192.168.0.116:9097,192.168.0.116:9098,192.168.0.116:9099 --topic test7</code></pre><p><img src="https://img.huangge1199.cn/blog/dpKafkaZKCluster/2022-07-08-15-14-23-image.png" alt="" /></p><h2 id="3创建消费者">3、创建消费者</h2><p>在kafka的bin\windows目录下执行下面的命令</p><pre><code class="language-batch">kafka-console-consumer.bat --bootstrap-server 192.168.0.116:9097 --topic test7 --from-beginning</code></pre><p><img src="https://img.huangge1199.cn/blog/dpKafkaZKCluster/2022-07-08-15-14-57-image.png" alt="" /></p><h2 id="4生产者发送消息消费者接收消息">4、生产者发送消息，消费者接收消息</h2><p>生产者随意输入内容，消费者显示内容</p><p>生产者：</p><p><img src="https://img.huangge1199.cn/blog/dpKafkaZKCluster/2022-07-08-15-15-55-image.png" alt="" /></p><p>消费者：</p><p><img src="https://img.huangge1199.cn/blog/dpKafkaZKCluster/2022-07-08-15-16-48-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Fri, 08 Jul 2022 10:42:20 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin下安装docker-compose]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin下安装docker-compose</link>
                    <description>
                            <![CDATA[<h1 id="下载文件">下载文件</h1><pre><code class="language-shell">sudo wget -c -t 0 https://github.com/docker/compose/releases/download/1.26.0/docker-compose-`uname -s`-`uname -m` -O /usr/local/bin/docker-compose</code></pre><p><img src="https://img.huangge1199.cn/blog/inDCByOsDeepin/2022-07-05-21-18-26-image.png" alt="" /></p><h1 id="添加执行权限">添加执行权限</h1><pre><code class="language-shell">sudo chmod a+rx /usr/local/bin/docker-compose</code></pre><p><img src="https://img.huangge1199.cn/blog/inDCByOsDeepin/2022-07-05-21-20-08-image.png" alt="" /></p><h1 id="验证是否安装成功">验证是否安装成功</h1><pre><code class="language-shell">docker-compose -v</code></pre><p><img src="https://img.huangge1199.cn/blog/inDCByOsDeepin/2022-07-05-21-20-46-image.png" alt="" /></p><h1 id="卸载">卸载</h1><pre><code class="language-shell">sudo rm /usr/local/bin/docker-compose</code></pre>]]>
                    </description>
                    <pubDate>Tue, 05 Jul 2022 21:57:49 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Jenkins通过kubernetes plugin连接K8s集群]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/jenkins通过kubernetesplugin连接k8s集群</link>
                    <description>
                            <![CDATA[<h2 id="一jenkins安装kubernetes-plugin插件">一、Jenkins安装kubernetes plugin插件</h2><h3 id="11-点击左侧系统管理">1.1 点击左侧系统管理</h3><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-26-15-image.png" alt="" /></p><h3 id="12-点击插件管理">1.2 点击插件管理</h3><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-27-34-image.png" alt="" /></p><h3 id="13-安装插件kubernetes-plugin">1.3 安装插件Kubernetes plugin</h3><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-28-53-image.png" alt="" /></p><h3 id="14-安装好后重启jenkins">1.4 安装好后重启Jenkins</h3><p>浏览器输入<a href="http://192.168.0.196:8080/restart，页面点击“是”重启Jenkins">http://192.168.0.196:8080/restart，页面点击“是”重启Jenkins</a></p><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-30-31-image.png" alt="" /></p><h2 id="二进入配置页">二、进入配置页</h2><h3 id="21-左侧点击系统管理">2.1 左侧点击系统管理</h3><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-31-51-image.png" alt="" /></p><h3 id="22-点击节点管理">2.2 点击节点管理</h3><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-32-25-image.png" alt="" /></p><h3 id="23-点击configure-clouds">2.3 点击Configure Clouds</h3><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-33-15-image.png" alt="" /></p><h2 id="三配置">三、配置</h2><h3 id="31-下拉框选择kubernetes">3.1 下拉框选择Kubernetes</h3><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-34-14-image.png" alt="" /></p><h3 id="32-点击kubernetes-cloud-details进入配置详情页">3.2 点击Kubernetes Cloud details...进入配置详情页</h3><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-35-00-image.png" alt="" /></p><h3 id="33-填入认证信息">3.3 填入认证信息</h3><p>需要填写红框内的4个内容</p><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-42-49-image.png" alt="" /></p><h4 id="kubernetes-地址">Kubernetes 地址</h4><p>这个通过命令行 查看</p><pre><code class="language-shell">kubectl cluster-info</code></pre><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-23-16-34-image.png" alt="" /></p><p>红框内的就是地址</p><h4 id="kubernetes-服务证书-key">Kubernetes 服务证书 key</h4><p>为/root/.kube/config中的certificate-authority-data部分，并通过base64加密</p><p>终端输入下面的命令查看certificate-authority-data：</p><pre><code class="language-shell">cat .kube/config</code></pre><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-46-52-image.png" alt="" /></p><p>在执行下面的命令进行base64加密：</p><pre><code class="language-shell">echo &quot;certificate-authority-data冒号后面的内容&quot; | base64 -d</code></pre><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-52-27-image.png" alt="" /></p><p>红框的内容填入“Kubernetes 服务证书 key”中</p><h4 id="kubernetes-命名空间">Kubernetes 命名空间</h4><p>使用default默认就好</p><h4 id="凭据">凭据</h4><p>这地方需要添加一个凭借</p><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-54-22-image.png" alt="" /></p><p>在弹出的页面中类型选Secret text</p><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-22-57-05-image.png" alt="" /></p><p>下面的Secret通过终端添加：</p><ul><li>创建一个</li></ul><pre><code class="language-shell">kubectl create sa jenkins</code></pre><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-23-19-07-image.png" alt="" /></p><p>获取token名</p><pre><code class="language-shell">kubectl describe sa jenkins</code></pre><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-23-19-57-image.png" alt="" /></p><p>获取token值</p><pre><code class="language-shell">kubectl describe secrets jenkins-token-szvg9 -n default</code></pre><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-23-21-36-image.png" alt="" /></p><p>上图中的token即为Secret填入的内容</p><p>最后的描述可以随意填写</p><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-23-07-30-image.png" alt="" /></p><p>点击添加，凭据就好了</p><h2 id="四验证">四、验证</h2><p>点击连接测试，左侧显示k8s集群版本</p><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-23-23-35-image.png" alt="" /></p><p>下面把Jenkins地址填上，再点击保存按钮就完成了</p><p><img src="https://img.huangge1199.cn/blog/bindK8sToJenkins/2022-06-28-23-26-16-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Tue, 28 Jun 2022 22:21:47 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[helm不需要证书安装rancher]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/helm不需要证书安装rancher</link>
                    <description>
                            <![CDATA[<h2 id="前置">前置</h2><p>安装好k8s和helm</p><p><img src="https://img.huangge1199.cn/blog/inRancherByHelmNoCert/2022-06-26-16-48-16-image.png" alt="" /></p><h2 id="安装命令">安装命令</h2><pre><code class="language-shell">helm install rancher rancher-stable/rancher \  --namespace cattle-system \  --set hostname=rancher.my.org \  --set replicas=1 \  --set ingress.tls.source=secret</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelmNoCert/2022-06-26-16-49-50-image.png" alt="" /></p><h2 id="设置域名映射">设置域名映射</h2><pre><code class="language-shell">sudo vi /etc/hosts# 添加域名映射 127.0.0.1 rancher.my.org# cat /etc/hosts</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelmNoCert/2022-06-26-16-51-20-image.png" alt="" /></p><h2 id="确认安装完成">确认安装完成</h2><pre><code class="language-shell">kubectl -n cattle-system get deploy rancher</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelmNoCert/2022-06-26-16-56-27-image.png" alt="" /></p><h2 id="浏览器访问">浏览器访问</h2><p>浏览器输入 <a href="https://rancher.my.org/">https://rancher.my.org/</a></p><p>高级--》继续访问</p><p><img src="https://img.huangge1199.cn/blog/inRancherByHelmNoCert/2022-06-26-16-59-29-image.png" alt="" /></p><h2 id="密码查看">密码查看</h2><p>终端输入，查看密码</p><pre><code class="language-shell">kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='{{.data.bootstrapPassword|base64decode}}{{&quot;\n&quot;}}'</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelmNoCert/2022-06-26-17-01-02-image.png" alt="" /></p><h2 id="设置自己好记的密码">设置自己好记的密码</h2><p>输入密码进入后，选择Set a specific password to use，然后下方设置自己的密码</p><p><img src="https://img.huangge1199.cn/blog/inRancherByHelmNoCert/2022-06-26-17-02-18-image.png" alt="" /></p><h2 id="进入的页面">进入的页面</h2><p><img src="https://img.huangge1199.cn/blog/inRancherByHelmNoCert/2022-06-26-17-04-28-image.png" alt="" /></p><p>至此，rancher部署完成</p>]]>
                    </description>
                    <pubDate>Sun, 26 Jun 2022 11:16:37 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin主机下通过Kubeadm方式安装K8S]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin主机下通过kubeadm方式安装k8s</link>
                    <description>
                            <![CDATA[<h1 id="1关闭swap">1、关闭swap</h1><p>依次执行下面的命令：</p><pre><code class="language-shell"># 查看分区的使用状态free -mh# 禁用swap分区sudo swapoff -a# 查看分区的使用状态free -mh</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadmByDeepin/2022-06-24-21-45-13-image.png" alt="" /></p><h1 id="2添加k8s源">2、添加k8s源</h1><p>编辑文件/etc/apt/sources.list.d/kubernetes.list</p><pre><code class="language-shell">sudo vi /etc/apt/sources.list.d/kubernetes.list</code></pre><p>插入以下内容：</p><pre><code class="language-yaml">deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main</code></pre><p>再执行命令查看：</p><pre><code class="language-shell">cat /etc/apt/sources.list.d/kubernetes.list</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadmByDeepin/2022-06-24-22-10-06-image.png" alt="" /></p><h1 id="3导入k8s密钥">3、导入k8s密钥</h1><p>执行命令：</p><pre><code class="language-shell">sudo curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadmByDeepin/2022-06-24-22-16-25-image.png" alt="" /></p><h1 id="4更新并安装kubeadm-kubelet-和-kubectl">4、更新并安装kubeadm, kubelet 和 kubectl</h1><p>执行命令：</p><pre><code class="language-shell">sudo apt-get updatesudo apt-get install kubelet kubeadm kubectl</code></pre><h1 id="5设置阿里云镜像加速">5、设置阿里云镜像加速</h1><p>编辑文件/etc/docker/daemon.json：</p><pre><code class="language-shell">sudo vi /etc/docker/daemon.json</code></pre><p>修改成如下内容：</p><pre><code class="language-yaml">{    &quot;registry-mirrors&quot;: [            &quot;https://{阿里云分配的地址}.mirror.aliyuncs.com&quot;,            &quot;https://registry-1.docker.io/v2/&quot;    ]}</code></pre><p>再执行命令查看：</p><pre><code class="language-shell">cat /etc/docker/daemon.json</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadmByDeepin/2022-06-24-22-49-46-image.png" alt="" /></p><h1 id="6拉取镜像">6、拉取镜像</h1><p>从阿里云拉取镜像并转换tag，执行命令如下：</p><pre><code class="language-shell">for  i  in  `kubeadm config images list`;  do    imageName=${i#k8s.gcr.io/}    docker pull registry.aliyuncs.com/google_containers/$imageName    docker tag registry.aliyuncs.com/google_containers/$imageName k8s.gcr.io/$imageName    docker rmi registry.aliyuncs.com/google_containers/$imageNamedone;</code></pre><p>如果有拉取不下来的，可以再上网找找镜像然后转换tag，或者直接执行下面的命令用docker官方镜像拉取，但是官方镜像拉取速度可能会很慢</p><pre><code class="language-shell">for  i  in  `kubeadm config images list`;  do    docker pull idone;</code></pre><h1 id="7kubeadm初始化">7、kubeadm初始化</h1><p>执行命令:</p><pre><code class="language-shell">kubeadm init --pod-network-cidr=10.244.0.0/16</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadmByDeepin/2022-06-24-23-00-07-image.png" alt="" /></p><h1 id="8执行提示的命令">8、执行提示的命令</h1><pre><code class="language-shell">mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadmByDeepin/2022-06-24-23-02-42-image.png" alt="" /></p><h1 id="9安装网络插件">9、安装网络插件</h1><p>执行命令：</p><pre><code class="language-shell">kubectl apply -f https://github.com/coreos/flannel/raw/master/Documentation/kube-flannel.yml</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadmByDeepin/2022-06-24-23-05-02-image.png" alt="" /></p><h1 id="10安装ingress">10、安装Ingress</h1><p>执行命令：</p><pre><code class="language-shell">kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml</code></pre><p>查看命令：</p><pre><code class="language-shell">kubectl get pods --all-namespaces</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadmByDeepin/2022-06-24-23-41-18-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Fri, 24 Jun 2022 21:39:51 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Helm安装Rancher]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/helm安装rancher</link>
                    <description>
                            <![CDATA[<h1 id="前置">前置</h1><p>本人是直接在<font color='red'>deeepin</font>系统上用rke安装的k8s集群形式，但是只有一个节点，<font color='red'>rke</font>是<font color='red'>1.3.10</font>版本的，安装好的<font color='red'>k8s</font>是<font color='red'>1.22.9</font>的版本</p><h1 id="前提条件----helm安装">前提条件 -- helm安装</h1><p>安照官网说明安装就可以：<a href="https://helm.sh/zh/docs/intro/install/">官网安装步骤</a></p><p>简单说明：</p><p>我这边是二进制形式安装的</p><ul><li>下载 需要的版本</li><li>解压(tar -zxvf helm-v3.9.0-linux-amd64.tar.gz)</li><li>在解压目中找到helm程序，移动到需要的目录中(mv linux-amd64/helm /usr/local/bin/helm)</li></ul><h1 id="1安装证书管理">1、安装证书管理</h1><blockquote><p>这里选用 Rancher 生成的 TLS 证书，因此需要 cert-manager</p></blockquote><h2 id="11-添加配置">1.1 添加配置</h2><p>执行命令：</p><pre><code class="language-shell">kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.5.1/cert-manager.crds.yaml</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/ca771f4aa96e9cafe14c329195308b345c23427b.png" alt="" /></p><h2 id="12-添加-jetstack-helm-仓库">1.2 添加 Jetstack Helm 仓库</h2><p>执行命令：</p><pre><code class="language-shell">helm repo add jetstack https://charts.jetstack.io</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-11-51-51-image.png" alt="" /></p><h2 id="13-更新本地-helm-chart-仓库缓存">1.3 更新本地 Helm chart 仓库缓存</h2><p>执行命令：</p><pre><code class="language-shell">helm repo update</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-11-53-22-image.png" alt="" /></p><h2 id="14-安装-cert-manager-helm-chart">1.4 安装 cert-manager Helm chart</h2><p>执行命令：</p><pre><code class="language-shell">helm install cert-manager jetstack/cert-manager \  --namespace cert-manager \  --create-namespace \  --version v1.5.1</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-11-55-29-image.png" alt="" /></p><p>如果报错内容如下：</p><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-11-56-38-image.png" alt="" /></p><p>可做如下操作：</p><pre><code class="language-shell"># 列出空间列表helm ls --all-namespaces# 删除kubectl delete namespace cert-manager# 列出空间列表helm ls --all-namespaces</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-11-59-43-image.png" alt="" /></p><p>然后在重新执行命令即可</p><h2 id="15-确认安装成功">1.5 确认安装成功</h2><p>执行命令：</p><pre><code class="language-shell">kubectl get pods --namespace cert-manager</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-12-02-27-image.png" alt="" /></p><h1 id="2安装ingress-nginx">2、安装ingress-nginx</h1><h2 id="21-添加ingress-nginx-repo">2.1 添加ingress-nginx repo</h2><p>执行命令：</p><pre><code class="language-shell">helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-12-10-24-image.png" alt="" /></p><h2 id="22-安装">2.2 安装</h2><p>执行命令：</p><pre><code class="language-shell">helm install ingress-nginx ingress-nginx/ingress-nginx -n kube-system \</code></pre><h1 id="3安装rancher">3、安装rancher</h1><h2 id="31-添加rancher-repo">3.1 添加rancher repo</h2><p>执行命令：</p><pre><code class="language-shell">helm repo add rancher-stable https://releases.rancher.com/server-charts/stable</code></pre><h2 id="32-查看列表">3.2 查看列表</h2><p>执行命令：</p><pre><code class="language-shell">helm repo list</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-12-12-48-image.png" alt="" /></p><h2 id="33-安装">3.3 安装</h2><pre><code class="language-shell">helm install rancher rancher-stable/rancher \&gt;   --namespace cattle-system \&gt;   --create-namespace \&gt;   --set hostname=rancher.my.org \&gt;   --no-hooks \&gt;   --version 2.6.5</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-12-18-50-image.png" alt="" /></p><blockquote><p>配置的hostname=rancher.my.org，这个域名需要添加到 <code>/etc/hosts</code></p></blockquote><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-12-24-48-image.png" alt="" /></p><h2 id="34-运行">3.4 运行</h2><pre><code class="language-shell">kubectl -n cattle-system rollout status deploy/rancher</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-12-19-21-image.png" alt="" /></p><h2 id="35-查看-rancher-运行状态">3.5 查看 Rancher 运行状态</h2><pre><code class="language-shell">kubectl -n cattle-system get deploy rancher</code></pre><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-12-20-59-image.png" alt="" /></p><p>至此，Rancher部署完成</p><h2 id="36-浏览器查看">3.6 浏览器查看</h2><p><a href="https://rancher.my.org/">https://rancher.my.org/</a> ，进入后简单配置下就可以了</p><p>默认密码在终端输入下面的命令，显示的就是默认密码，之后可以修改成自己好记的密码</p><pre><code class="language-shell">kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='{{.data.bootstrapPassword|base64decode}}{{&quot;\n&quot;}}'</code></pre><p>进入后的样子：</p><p><img src="https://img.huangge1199.cn/blog/inRancherByHelm/2022-06-18-12-27-32-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sat, 18 Jun 2022 10:44:37 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin下安装hexo]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin下安装hexo</link>
                    <description>
                            <![CDATA[<h1 id="1%E3%80%81%E5%89%8D%E7%BD%AE%E6%9D%A1%E4%BB%B6" tabindex="-1">1、前置条件</h1><p>安装好nodejs</p><p>参考：<a href="https://blog.huangge1199.cn/?p=47" target="_blank">deepin下安装nodejs</a></p><h1 id="2%E3%80%81%E5%85%A8%E5%B1%80%E5%AE%89%E8%A3%85hexo" tabindex="-1">2、全局安装Hexo</h1><p>执行命令：</p><pre><code class="language-shell">npm install -g hexo-cli</code></pre><p><img src="https://img.huangge1199.cn/blog/inHexoByOsDeepin/image-20220603141423311.png" alt="" /></p><h1 id="3%E3%80%81%E5%88%9B%E5%BB%BA%E8%BD%AF%E9%93%BE%E6%8E%A5" tabindex="-1">3、创建软链接</h1><pre><code class="language-shell"># 创建hexo软链接sudo ln -s /home/deepin/app/node/bin/hexo /usr/local/bin/# 查看软链接列表sudo ls -l /usr/local/bin/</code></pre><p><img src="https://img.huangge1199.cn/blog/inHexoByOsDeepin/image-20220603141527169.png" alt="" /></p><h1 id="4%E3%80%81%E6%9F%A5%E7%9C%8Bhexo%E7%89%88%E6%9C%AC" tabindex="-1">4、查看Hexo版本</h1><pre><code class="language-shell">hexo -v</code></pre><p><img src="https://img.huangge1199.cn/blog/inHexoByOsDeepin/image-20220603141613298.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Fri, 03 Jun 2022 14:51:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin下安装nodejs]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin下安装nodejs</link>
                    <description>
                            <![CDATA[<h1 id="1下载安装包">1、下载安装包</h1><p><a href="https://nodejs.org/en/download/">官网地址</a></p><p><img src="https://img.huangge1199.cn/blog/inNodejsByOsDeepin/image-20220603110634896.png" alt="image-20220603110634896" /></p><blockquote><p>注意版本</p></blockquote><h1 id="2解压安装包">2、解压安装包</h1><p>执行命令：</p><pre><code class="language-shell"># 解压命令tar -xf node-v16.15.1-linux-x64.tar.xz# 查看列表ls -l</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByOsDeepin/image-20220603111656347.png" alt="image-20220603111656347" /></p><h1 id="3移动文件">3、移动文件</h1><p>这步是为了方便找到自己安装的软件，可做可不做</p><p>我这边是统一移动到用户的app目录下</p><pre><code class="language-shell"># 移动文件mv node-v16.15.1-linux-x64 ../app/# 查看列表ls -l# 切换目录cd ../app/# 查看列表ls -l# 更改名称（目录名过长）mv node-v16.15.1-linux-x64 node# 查看列表ls -l</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByOsDeepin/image-20220603112001543.png" alt="image-20220603112001543" /></p><p><img src="https://img.huangge1199.cn/blog/inNodejsByOsDeepin/image-20220603112153367.png" alt="image-20220603112153367" /></p><h1 id="4创建软链接">4、创建软链接</h1><pre><code class="language-shell"># 创建node软链接sudo ln -s /home/deepin/app/node/bin/node /usr/local/bin/# 创建npm软链接sudo ln -s /home/deepin/app/node/bin/npm  /usr/local/bin/# 确认软链接建立好了sudo ls -l /usr/local/bin/</code></pre><blockquote><p>此处涉及到权限问题，因此命令前要加<code>sudo</code></p></blockquote><p><img src="https://img.huangge1199.cn/blog/inNodejsByOsDeepin/image-20220603112904096.png" alt="image-20220603112904096" /></p><h1 id="5确认node和npm版本">5、确认node和npm版本</h1><pre><code class="language-shell">node -vnpm -v</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByOsDeepin/image-20220603113034176.png" alt="image-20220603113034176" /></p><h1 id="6设置镜像">6、设置镜像</h1><p>设置国内淘宝的镜像，提高npm的下载速度</p><pre><code class="language-shell"># 查看npm配置列表npm config list# 执行如下命令设置成国内的镜像npm config set registry https://registry.npm.taobao.org# 查看npm配置列表npm config list</code></pre><p><img src="https://img.huangge1199.cn/blog/inNodejsByOsDeepin/image-20220603113454241.png" alt="image-20220603113454241" /></p>]]>
                    </description>
                    <pubDate>Fri, 03 Jun 2022 14:51:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin下安装vue]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin下安装vue</link>
                    <description>
                            <![CDATA[<h1 id="1前置条件">1、前置条件</h1><p>安装好nodejs</p><p>参考：<a href="http://192.168.0.198:5080/post/inNodejsByOsDeepin/">deepin下安装nodejs</a></p><h1 id="2全局安装vue">2、全局安装Vue</h1><p>执行命令：</p><pre><code class="language-shell"># 下面两个版本的二选一哦npm install -g @vue/cli//vue3.0npm install -g vue-cli//vue2.0</code></pre><p>我这边安装的是3.0版本的</p><p><img src="https://img.huangge1199.cn/blog/inVueByOsDeepin/image-20220603114033774.png" alt="image-20220603114033774" /></p><h1 id="3全局安装webpack">3、全局安装webpack</h1><pre><code class="language-shell">npm install -g webpack</code></pre><p><img src="https://img.huangge1199.cn/blog/inVueByOsDeepin/image-20220603114219499.png" alt="image-20220603114219499" /></p><h1 id="4创建软链接">4、创建软链接</h1><pre><code class="language-shell"># 创建Vue软链接sudo ln -s /home/deepin/app/node/bin/vue /usr/local/bin/# 创建webpack软链接sudo ln -s /home/deepin/app/node/bin/webpack /usr/local/bin/# 查看软链接列表sudo ls -l /usr/local/bin/</code></pre><p><img src="https://img.huangge1199.cn/blog/inVueByOsDeepin/image-20220603114513508.png" alt="image-20220603114513508" /></p><h1 id="5查看vue版本">5、查看Vue版本</h1><pre><code class="language-shell">vue --version</code></pre><p><img src="https://img.huangge1199.cn/blog/inVueByOsDeepin/image-20220603114616673.png" alt="image-20220603114616673" /></p>]]>
                    </description>
                    <pubDate>Fri, 03 Jun 2022 14:51:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin下安装Maven]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin下安装maven</link>
                    <description>
                            <![CDATA[<h1 id="1前置条件">1、前置条件</h1><p>安装好jdk</p><p>参考：<a href="http://192.168.0.198:5080/post/inJdkByOsDeepin/">deepin下安装jdk</a></p><h1 id="2下载安装包">2、下载安装包</h1><p>官网地址：<a href="https://maven.apache.org/download.cgi">maven下载页面</a></p><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603115702494.png" alt="image-20220603115702494" /></p><p>我这边下载的是3.8.5版本的，如果下载其他版本，用下面的链接：</p><p><a href="https://img.huangge1199.cn/blog/inMavenByOsDeepin/https://archive.apache.org/dist/maven/maven-3/">其他版本maven</a></p><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603120000596.png" alt="image-20220603120000596" /></p><h1 id="3解压">3、解压</h1><pre><code class="language-shell">tar -xf apache-maven-3.8.5-bin.tar.gzls -l</code></pre><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603120156243.png" alt="image-20220603120156243" /></p><h1 id="4移动">4、移动</h1><pre><code class="language-shell">mv apache-maven-3.8.5 ../app/ls -lls -l ../app/</code></pre><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603120347751.png" alt="image-20220603120347751" /></p><h1 id="5配置环境变量">5、配置环境变量</h1><pre><code class="language-shell">sudo vi /etc/profile</code></pre><p>文件最下面加入下面的内容</p><pre><code># configuration maven development enviroumentexport MAVEN_HOME=/home/deepin/app/apache-maven-3.8.5export PATH=$PATH:$MAVEN_HOME/bin</code></pre><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603121051155.png" alt="image-20220603121051155" /></p><p>执行命令让配置文件生效：</p><pre><code class="language-shell">source /etc/profile</code></pre><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603121153205.png" alt="image-20220603121153205" /></p><h1 id="6验证">6、验证</h1><p>查看maven版本做验证：</p><pre><code class="language-shell">mvn -v</code></pre><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603121458972.png" alt="image-20220603121458972" /></p><h1 id="7配置仓库文件目录">7、配置仓库文件目录</h1><pre><code class="language-shell">vi /home/deepin/app/apache-maven-3.8.5/conf/settings.xml</code></pre><p>找到localRepository，在下方加入下面的内容：</p><pre><code class="language-xml">&lt;localRepository&gt;/home/deepin/repo&lt;/localRepository&gt;</code></pre><p>红框内容为新加入的</p><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603122200505.png" alt="image-20220603122200505" /></p><h1 id="8添加阿里镜像源">8、添加阿里镜像源</h1><pre><code class="language-shell">vi /home/deepin/app/apache-maven-3.8.5/conf/settings.xml</code></pre><p>找到mirror添加下面的内容：</p><pre><code class="language-xml">&lt;mirror&gt;    &lt;id&gt;alimaven&lt;/id&gt;    &lt;mirrorOf&gt;*&lt;/mirrorOf&gt;    &lt;name&gt;aliyun maven&lt;/name&gt;    &lt;url&gt;https://maven.aliyun.com/repository/public&lt;/url&gt;&lt;/mirror&gt;</code></pre><p>下面红框内容为添加的</p><p><img src="https://img.huangge1199.cn/blog/inMavenByOsDeepin/image-20220603122859128.png" alt="image-20220603122859128" /></p>]]>
                    </description>
                    <pubDate>Fri, 03 Jun 2022 14:46:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin下安装jdk]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin下安装jdk</link>
                    <description>
                            <![CDATA[<h1 id="1jdk-下载">1、jdk 下载</h1><p><a href="https://www.oracle.com/java/technologies/downloads/#java11">官网下载地址</a>如下：</p><p><img src="https://img.huangge1199.cn/blog/inJdkByOsDeepin/image-20220603015920009.png" alt="image-20220603015920009" /></p><blockquote><p>注意区分是哪个版本的</p></blockquote><h1 id="2安装deb包">2、安装deb包</h1><p>终端进入到deb文件所在目录，执行安装命令：</p><pre><code class="language-shell">sudo dpkg -i jdk-11.0.15.1_linux-x64_bin.deb</code></pre><p><img src="https://img.huangge1199.cn/blog/inJdkByOsDeepin/image-20220603020439325.png" alt="image-20220603020439325" /></p><h1 id="3配置环境变量">3、配置环境变量</h1><p>终端执行命令：</p><pre><code class="language-shell">sudo vi /etc/profile</code></pre><p>然后输入密码，在文件的最后加上下面的内容</p><pre><code>#configuration java development enviroumentexport JAVA_HOME=/usr/lib/jvm/jdk-11export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar </code></pre><h1 id="4使环境变量生效">4、使环境变量生效</h1><p>执行命令：</p><pre><code class="language-shell">source /etc/profile</code></pre><p><img src="https://img.huangge1199.cn/blog/inJdkByOsDeepin/image-20220603021925685.png" alt="image-20220603021925685" /></p><h1 id="5检查是否成功">5、检查是否成功</h1><p>执行命令：</p><pre><code class="language-she">java -version</code></pre><p><img src="https://img.huangge1199.cn/blog/inJdkByOsDeepin/image-20220603022022217.png" alt="image-20220603022022217" /></p>]]>
                    </description>
                    <pubDate>Fri, 03 Jun 2022 14:43:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[deepin下安装git]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/deepin下安装git</link>
                    <description>
                            <![CDATA[<blockquote><p>个人建议直接终端安装，下面的安装也是终端命令行安装的</p></blockquote><h1 id="1安装git">1、安装git</h1><p>执行安装命令：</p><pre><code class="language-she">sudo apt-get install git</code></pre><p><img src="inGitByOsDeepin/image-20220603103757605.png" alt="image-20220603103757605" /></p><h1 id="2确认git安装成功">2、确认git安装成功</h1><p>执行查看git版本的命令，以此确认安装成功</p><pre><code class="language-shell">git --version</code></pre><p><img src="inGitByOsDeepin/image-20220603103959058.png" alt="image-20220603103959058" /></p><h1 id="3配置git全局用户名和邮箱">3、配置git全局用户名和邮箱</h1><p>配置全局用户名：</p><pre><code class="language-shell">git config --global user.name &quot;用户名&quot;</code></pre><p><img src="inGitByOsDeepin/image-20220603104225094.png" alt="image-20220603104225094" /></p><p>配置全局邮箱：</p><pre><code class="language-shel">git config --global user.email &quot;邮箱&quot;</code></pre><p><img src="inGitByOsDeepin/image-20220603104335664.png" alt="image-20220603104335664" /></p><h1 id="4确认配置结果">4、确认配置结果</h1><p>查看配置信息确认</p><pre><code class="language-shell">git config --list</code></pre><p><img src="inGitByOsDeepin/image-20220603104447770.png" alt="image-20220603104447770" /></p>]]>
                    </description>
                    <pubDate>Fri, 03 Jun 2022 14:30:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[docker构建自定义镜像]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/docker构建自定义镜像</link>
                    <description>
                            <![CDATA[<h1 id="1编写dockerfile">1、编写Dockerfile</h1><p>Dockerfile</p><pre><code>FROM nginxRUN apt update &amp;&amp; apt install -y vim</code></pre><h1 id="2构建镜像">2、构建镜像</h1><p>执行命令：</p><pre><code class="language-shell">docker build -t vim-nginx:1 .</code></pre><blockquote><p>注：要在Dockerfile所在目录下执行</p></blockquote><p>这步时间较长，多等等，出现下面红框表示安装成功</p><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-05-31-16-21-41-image.png" alt="" /></p><p>完成后，执行命令确认镜像生成：</p><pre><code class="language-shell">docker images</code></pre><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-05-31-16-28-07-image.png" alt="" /></p><h1 id="3测试镜像">3、测试镜像</h1><p>启动容器：</p><pre><code class="language-shell">docker run -d --name new-nginx vim-nginx:1docker ps -a</code></pre><p>下面红框内是执行过程，中间的部分我命令敲错了，忽略掉</p><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-05-31-16-32-54-image.png" alt="" /></p><p>进入容器使用vim命令：</p><pre><code class="language-shell">docker exec -it new-nginx bashvim 123.txtexit</code></pre><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-05-31-17-08-23-image.png" alt="" /></p><p>停止容器：</p><pre><code class="language-shell">docker stop new-nginxdocker ps -a</code></pre><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-05-31-17-11-22-image.png" alt="" /></p><p>删除容器：</p><pre><code class="language-shell">docker rm new-nginxdocker ps -a</code></pre><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-05-31-17-12-17-image.png" alt="" /></p><h1 id="4docker登录">4、docker登录</h1><p>执行命令：</p><pre><code class="language-shell">docker login</code></pre><p>然后输入用户名和密码</p><blockquote><p>注：用户名不是登录的邮箱</p></blockquote><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-05-31-17-22-07-image.png" alt="" /></p><h1 id="5镜像修改">5、镜像修改</h1><p>tag命令修改为规范的镜像：</p><pre><code class="language-shell">docker tag vim-nginx:1 huangge1199/vim-nginx:1docker images</code></pre><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-05-31-17-30-38-image.png" alt="" /></p><h1 id="6推送镜像">6、推送镜像</h1><pre><code class="language-shell">docker push huangge1199/vim-nginx:1</code></pre><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-06-01-08-37-12-image.png" alt="" /></p><p>网页进入自己的docker仓库：</p><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-06-01-08-38-34-image.png" alt="" /></p><h1 id="7删除本地镜像">7、删除本地镜像</h1><pre><code class="language-shell">docker rmi huangge1199/vim-nginx:1docker images</code></pre><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-06-01-08-39-28-image.png" alt="" /></p><h1 id="8拉取镜像">8、拉取镜像</h1><pre><code class="language-shell">docker pull huangge1199/vim-nginx:1docker images</code></pre><p><img src="https://img.huangge1199.cn/blog/createMyImage/2022-06-01-08-42-46-image.png" alt="" /></p><h1 id="9重复3的步骤测试镜像">9、重复3的步骤测试镜像</h1><blockquote><p>注意步骤3和现在的镜像名可能不同，记得替换</p></blockquote>]]>
                    </description>
                    <pubDate>Tue, 31 May 2022 15:07:55 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[通过Kubeadm方式安装K8S]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/通过kubeadm方式安装k8s</link>
                    <description>
                            <![CDATA[<h1 id="前言">前言</h1><p>根据前几次的经验，这一次，运用脚本的形式安装，可以节约大部分的步骤，把一些前置的配置什么的写到shell脚本里面，随着<code>vagrant up</code>启动命令一起安装</p><p>集群环境：</p><table><thead><tr><th align="center"> </th><th>IP</th><th>内存</th><th>CPU核数</th></tr></thead><tbody><tr><td align="center">master</td><td>172.17.8.51</td><td>4G</td><td>2</td></tr><tr><td align="center">node</td><td>172.17.8.52</td><td>4G</td><td>1</td></tr><tr><td align="center">node</td><td>172.17.8.53</td><td>4G</td><td>1</td></tr></tbody></table><h1 id="1编写vagrantfile文件">1、编写Vagrantfile文件</h1><p>Vagrantfile内容：</p><pre><code># -*- mode: ruby -*-# vi: set ft=ruby :# on win10, you need `vagrant plugin install vagrant-vbguest --plugin-version 0.21` and change synced_folder.type=&quot;virtualbox&quot;# reference `https://www.dissmeyer.com/2020/02/11/issue-with-centos-7-vagrant-boxes-on-windows-10/`Vagrant.configure(&quot;2&quot;) do |config|  config.vm.box_check_update = false  config.vm.provider 'virtualbox' do |vb|  vb.customize [ &quot;guestproperty&quot;, &quot;set&quot;, :id, &quot;/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold&quot;, 1000 ]  end    $num_instances = 3  # curl https://discovery.etcd.io/new?size=3  (1..$num_instances).each do |i|    config.vm.define &quot;node#{i}&quot; do |node|      node.vm.box = &quot;centos/7&quot;      node.vm.hostname = &quot;node#{i}&quot;      ip = &quot;172.17.8.#{i+50}&quot;      node.vm.network &quot;private_network&quot;, ip: ip      node.vm.provider &quot;virtualbox&quot; do |vb|        vb.memory = &quot;4096&quot;        if i==1 then            vb.cpus = 2        else            vb.cpus = 1        end        vb.name = &quot;node#{i+50}&quot;      end    end  end  config.vm.provision &quot;shell&quot;, privileged: true, path: &quot;./setup.sh&quot;end</code></pre><h1 id="2编写启动后的脚本">2、编写启动后的脚本</h1><p>setup.sh内容：</p><pre><code class="language-shell">#/bin/sh# 安装docker相关依赖sudo yum install -y yum-utils# 添加阿里源sudo yum-config-manager \    --add-repo \    http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.reposudo sed -i 's/gpgcheck=1/gpgcheck=0/g' /etc/yum.repos.d/docker-ce.repo # 安装dockeryes y|sudo yum install docker-ce docker-ce-cli containerd.io# 启动dockersudo systemctl start dockeryy# 更改cgroup driver以及docker镜像仓库源sudo bash -c 'cat &gt; /etc/docker/daemon.json &lt;&lt;EOF{  &quot;exec-opts&quot;: [&quot;native.cgroupdriver=systemd&quot;],  &quot;log-driver&quot;: &quot;json-file&quot;,  &quot;log-opts&quot;: {    &quot;max-size&quot;: &quot;100m&quot;  },  &quot;storage-driver&quot;: &quot;overlay2&quot;,  &quot;storage-opts&quot;: [    &quot;overlay2.override_kernel_check=true&quot;  ],  &quot;registry-mirrors&quot;: [    &quot;https://registry.docker-cn.com&quot;,    &quot;http://hub-mirror.c.163.com&quot;,    &quot;https://w5a7th34.mirror.aliyuncs.com&quot;,    &quot;http://f1361db2.m.daocloud.io&quot;,    &quot;https://mirror.ccs.tencentyun.com&quot;  ]}EOF'# 添加docker组if [ ! $(getent group docker) ];then     sudo groupadd docker;else    echo &quot;docker user group already exists&quot;fisudo gpasswd -a $USER docker# 加载、重启dockersudo systemctl  daemon-reloadsudo systemctl restart dockersudo sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_configsudo systemctl restart sshdsudo bash -c 'cat &lt;&lt;EOF &gt; /etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64enabled=1gpgcheck=0repo_gpgcheck=0gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpghttps://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpgEOF'sudo setenforce 0# 安装kubeadm, kubectl, kubeletsudo yum install -y kubelet-1.23.6 kubeadm-1.23.6 kubectl-1.23.6 --disableexcludes=kubernetes# 设置docker和kubelet开机自启并启动sudo systemctl enable docker &amp;&amp; systemctl start dockersudo systemctl enable kubelet &amp;&amp; systemctl start kubelet# 设置网络桥接sudo bash -c 'cat &lt;&lt;EOF &gt;  /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-ip6tables = 1net.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward=1EOF'sudo sysctl --system# 关闭防火墙sudo systemctl stop firewalldsudo systemctl disable firewalld# 关闭swapsudo swapoff -a# 设置开机自启sudo systemctl enable docker.servicesudo systemctl enable kubelet.service</code></pre><h1 id="3启动">3、启动</h1><p>注：Vagrantfile和setup.sh放在同一目录，并且在该目录下执行启动命令：</p><pre><code class="language-shell">vagrant up</code></pre><p>由于在启动中加入了脚本，此次启动执行的内容多，时间要比以往长些</p><h1 id="4通过远程连接工具连接">4、通过远程连接工具连接</h1><p>目前，三台机器分别有两个用户，root和vagrant，密码全部是vagrant</p><h1 id="5部署主节点">5、部署主节点</h1><p>注：这步时间较长，耐心等待</p><pre><code class="language-shell">kubeadm init \--apiserver-advertise-address=172.17.8.51 \--image-repository registry.aliyuncs.com/google_containers \--kubernetes-version v1.23.6 \--service-cidr=10.96.0.0/12 \--pod-network-cidr=10.244.0.0/16</code></pre><p>出现下面的内容，主节点部署完成</p><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-18-24-59-image.png" alt="" /></p><h1 id="6使用-kubectl-工具">6、使用 kubectl 工具</h1><p>执行命令：</p><pre><code class="language-shell">mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config</code></pre><h1 id="7安装-pod-网络插件cni">7、安装 Pod 网络插件（CNI）</h1><p>执行命令：</p><pre><code class="language-shell">kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml</code></pre><p>注：如果连接不上，可以在当前目录新建文件kube-flannel.yml替换掉文件</p><p>kube-flannel.yml内容：</p><pre><code class="language-yml">---apiVersion: policy/v1beta1kind: PodSecurityPolicymetadata:  name: psp.flannel.unprivileged  annotations:    seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default    seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default    apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default    apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/defaultspec:  privileged: false  volumes:  - configMap  - secret  - emptyDir  - hostPath  allowedHostPaths:  - pathPrefix: &quot;/etc/cni/net.d&quot;  - pathPrefix: &quot;/etc/kube-flannel&quot;  - pathPrefix: &quot;/run/flannel&quot;  readOnlyRootFilesystem: false  # Users and groups  runAsUser:    rule: RunAsAny  supplementalGroups:    rule: RunAsAny  fsGroup:    rule: RunAsAny  # Privilege Escalation  allowPrivilegeEscalation: false  defaultAllowPrivilegeEscalation: false  # Capabilities  allowedCapabilities: ['NET_ADMIN', 'NET_RAW']  defaultAddCapabilities: []  requiredDropCapabilities: []  # Host namespaces  hostPID: false  hostIPC: false  hostNetwork: true  hostPorts:  - min: 0    max: 65535  # SELinux  seLinux:    # SELinux is unused in CaaSP    rule: 'RunAsAny'---kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata:  name: flannelrules:- apiGroups: ['extensions']  resources: ['podsecuritypolicies']  verbs: ['use']  resourceNames: ['psp.flannel.unprivileged']- apiGroups:  - &quot;&quot;  resources:  - pods  verbs:  - get- apiGroups:  - &quot;&quot;  resources:  - nodes  verbs:  - list  - watch- apiGroups:  - &quot;&quot;  resources:  - nodes/status  verbs:  - patch---kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:  name: flannelroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: flannelsubjects:- kind: ServiceAccount  name: flannel  namespace: kube-system---apiVersion: v1kind: ServiceAccountmetadata:  name: flannel  namespace: kube-system---kind: ConfigMapapiVersion: v1metadata:  name: kube-flannel-cfg  namespace: kube-system  labels:    tier: node    app: flanneldata:  cni-conf.json: |    {      &quot;name&quot;: &quot;cbr0&quot;,      &quot;cniVersion&quot;: &quot;0.3.1&quot;,      &quot;plugins&quot;: [        {          &quot;type&quot;: &quot;flannel&quot;,          &quot;delegate&quot;: {            &quot;hairpinMode&quot;: true,            &quot;isDefaultGateway&quot;: true          }        },        {          &quot;type&quot;: &quot;portmap&quot;,          &quot;capabilities&quot;: {            &quot;portMappings&quot;: true          }        }      ]    }  net-conf.json: |    {      &quot;Network&quot;: &quot;10.244.0.0/16&quot;,      &quot;Backend&quot;: {        &quot;Type&quot;: &quot;vxlan&quot;      }    }---apiVersion: apps/v1kind: DaemonSetmetadata:  name: kube-flannel-ds  namespace: kube-system  labels:    tier: node    app: flannelspec:  selector:    matchLabels:      app: flannel  template:    metadata:      labels:        tier: node        app: flannel    spec:      affinity:        nodeAffinity:          requiredDuringSchedulingIgnoredDuringExecution:            nodeSelectorTerms:            - matchExpressions:              - key: kubernetes.io/os                operator: In                values:                - linux      hostNetwork: true      priorityClassName: system-node-critical      tolerations:      - operator: Exists        effect: NoSchedule      serviceAccountName: flannel      initContainers:      - name: install-cni-plugin       #image: flannelcni/flannel-cni-plugin:v1.1.0 for ppc64le and mips64le (dockerhub limitations may apply)        image: rancher/mirrored-flannelcni-flannel-cni-plugin:v1.1.0        command:        - cp        args:        - -f        - /flannel        - /opt/cni/bin/flannel        volumeMounts:        - name: cni-plugin          mountPath: /opt/cni/bin      - name: install-cni       #image: flannelcni/flannel:v0.18.0 for ppc64le and mips64le (dockerhub limitations may apply)        image: rancher/mirrored-flannelcni-flannel:v0.18.0        command:        - cp        args:        - -f        - /etc/kube-flannel/cni-conf.json        - /etc/cni/net.d/10-flannel.conflist        volumeMounts:        - name: cni          mountPath: /etc/cni/net.d        - name: flannel-cfg          mountPath: /etc/kube-flannel/      containers:      - name: kube-flannel       #image: flannelcni/flannel:v0.18.0 for ppc64le and mips64le (dockerhub limitations may apply)        image: rancher/mirrored-flannelcni-flannel:v0.18.0        command:        - /opt/bin/flanneld        args:        - --ip-masq        - --kube-subnet-mgr        resources:          requests:            cpu: &quot;100m&quot;            memory: &quot;50Mi&quot;          limits:            cpu: &quot;100m&quot;            memory: &quot;50Mi&quot;        securityContext:          privileged: false          capabilities:            add: [&quot;NET_ADMIN&quot;, &quot;NET_RAW&quot;]        env:        - name: POD_NAME          valueFrom:            fieldRef:              fieldPath: metadata.name        - name: POD_NAMESPACE          valueFrom:            fieldRef:              fieldPath: metadata.namespace        - name: EVENT_QUEUE_DEPTH          value: &quot;5000&quot;        volumeMounts:        - name: run          mountPath: /run/flannel        - name: flannel-cfg          mountPath: /etc/kube-flannel/        - name: xtables-lock          mountPath: /run/xtables.lock      volumes:      - name: run        hostPath:          path: /run/flannel      - name: cni-plugin        hostPath:          path: /opt/cni/bin      - name: cni        hostPath:          path: /etc/cni/net.d      - name: flannel-cfg        configMap:          name: kube-flannel-cfg      - name: xtables-lock        hostPath:          path: /run/xtables.lock          type: FileOrCreate</code></pre><h1 id="8节点加入集群">8、节点加入集群</h1><p>在第5步<code>kubeadm init</code>命令输出的日志中，最后几行有需要执行的命令，那个命令拿出来直接在node2和node3上运行就可以了（token的有效期是24小时，超过了需要重新生成）</p><p>当然，如果你想我一样，忘记复制了还恰好关掉了远程，那么就有两种方式可以解决</p><ul><li><p>通过生成新的token显示命令（对应8.1操作）</p></li><li><p>直接查看token（对应8.2操作）</p></li></ul><p>8.1、执行下面的命令生成新的token：</p><pre><code class="language-shell">kubeadm token create --print-join-command</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-19-27-17-image.png" alt="" /></p><p>这里显示的命令拿到要加入的节点（node2和node3）执行就可以加入集群中</p><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-19-28-18-image.png" alt="" /></p><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-19-30-16-image.png" alt="" /></p><p>然后回到master主节点执行命令，确认加入成功：</p><pre><code class="language-shell">kubectl get nodes</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-19-58-04-image.png" alt="" /></p><p>8.2、查看token命令获取</p><pre><code class="language-shell">kubeadm token list</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-20-54-08-image.png" alt="" /></p><p>主节点：</p><pre><code class="language-shell"># 查看节点kubectl get nodes</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-21-15-08-image.png" alt="" /></p><p>节点3：</p><pre><code class="language-shell">kubeadm join 172.17.8.51:6443 --token o15q87.xtnzlfis6gtez1x6 --discovery-token-unsafe-skip-ca-verification</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-21-22-21-image.png" alt="" /></p><p>主节点：</p><pre><code class="language-shell"># 查看节点kubectl get nodes</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-21-22-55-image.png" alt="" /></p><h1 id="9集群中移除节点">9、集群中移除节点</h1><p>主节点执行：</p><pre><code class="language-shell"># 查看节点kubectl get nodes# 移除节点3kubectl delete node node3# 查看节点kubectl get nodes</code></pre><p><img src="https://img.huangge1199.cn/blog/inK8sByKubeadm/2022-06-04-20-59-19-image.png" alt="" /></p><p>删除的节点执行：</p><pre><code class="language-shell">kubeadm resetsystemctl stop kubeletsystemctl stop dockerrm -rf /var/lib/cni/rm -rf /var/lib/kubelet/*rm -rf /etc/cni/systemctl start dockersystemctl start kubelet</code></pre>]]>
                    </description>
                    <pubDate>Tue, 31 May 2022 14:10:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[一条命令运行rancher]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/一条命令运行rancher</link>
                    <description>
                            <![CDATA[<h1 id="1rancher安装">1、rancher安装</h1><p>控制台中rke用户下执行docker命令：</p><pre><code class="language-shell">docker run --name=rancher -d --privileged --restart=unless-stopped -p 30040:80 -p 30050:443 rancher/rancher:latest</code></pre><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-05-14-image.png" alt="" /></p><h1 id="2检查是否正常启动">2、检查是否正常启动</h1><p>可通过下面两个命令查看：</p><pre><code class="language-shell">docker ps | grep rancher           ## 查看正在运行中的docker容器</code></pre><h1 id="3浏览器访问">3、浏览器访问</h1><p>输入<a href="https://IP:PORT">https://IP:PORT</a></p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-10-27-image.png" alt="" /></p><p>点击高级，然后点击继续前往</p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-11-34-image.png" alt="" /></p><h1 id="4密码">4、密码</h1><p>根据提示，输入并修改密码</p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-12-49-image.png" alt="" /></p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-13-57-image.png" alt="" /></p><p>浏览器输入密码后，选择红框的，并在下方输入自己想要设置的密码</p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-15-39-image.png" alt="" /></p><p>进入后里面有一个默认的k3s</p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-18-34-image.png" alt="" /></p><h1 id="5加入其他存在的集群">5、加入其他存在的集群</h1><p>点击<code>Import Existing</code></p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-19-37-image.png" alt="" /></p><p>选择<code>Generic</code></p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-20-46-image.png" alt="" /></p><p>集群名字随意输入，只要你能记住</p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-22-26-image.png" alt="" /></p><p>根据红框的操作执行命令注册进来</p><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-23-58-image.png" alt="" /></p><p>执行命令</p><pre><code class="language-shell">kubectl apply -f https://172.17.8.51:30050/v3/import/2llq4b95zbspwqlcjrb898dtwqmqgtcxtfxjdlkgp8c79jpzf8tfn6_c-m-5ffgdfz6.yaml</code></pre><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-24-48-image.png" alt="" /></p><p>报了认证的问题，执行第二个命令</p><pre><code class="language-shell">curl --insecure -sfL https://172.17.8.51:30050/v3/import/2llq4b95zbspwqlcjrb898dtwqmqgtcxtfxjdlkgp8c79jpzf8tfn6_c-m-5ffgdfz6.yaml | kubectl apply -f -</code></pre><p><img src="https://img.huangge1199.cn/blog/inReacherByDC/2022-06-04-20-26-55-image.png" alt="" /></p><p>我这边是运行成功了</p>]]>
                    </description>
                    <pubDate>Thu, 26 May 2022 08:49:12 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣675. 为高尔夫比赛砍树]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣675为高尔夫比赛砍树</link>
                    <description>
                            <![CDATA[<p>2022年05月24日 力扣每日一题</p><p><a href="https://leetcode.cn/problems/cut-off-trees-for-golf-event/">675. 为高尔夫比赛砍树</a></p><h1 id="题目">题目</h1><p>你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 <code>m x n</code> 的矩阵表示， 在这个矩阵中：</p><ul>     <li><code>0</code> 表示障碍，无法触碰</li>     <li><code>1</code> 表示地面，可以行走</li>     <li><code>比 1 大的数</code> 表示有树的单元格，可以行走，数值表示树的高度</li>  </ul><p>每一步，你都可以向上、下、左、右四个方向之一移动一个单位，如果你站的地方有一棵树，那么你可以决定是否要砍倒它。</p><p>你需要按照树的高度从低向高砍掉所有的树，每砍过一颗树，该单元格的值变为 <code>1</code>（即变为地面）。</p><p>你将从 <code>(0, 0)</code> 点开始工作，返回你砍完所有树需要走的最小步数。 如果你无法砍完所有的树，返回 <code>-1</code> 。</p><p>可以保证的是，没有两棵树的高度是相同的，并且你至少需要砍倒一棵树。</p><p> </p><p><strong>示例 1：</strong></p>  <img alt="" src="https://assets.leetcode.com/uploads/2020/11/26/trees1.jpg" style="width: 242px; height: 242px;" />  <pre>  <strong>输入：</strong>forest = [[1,2,3],[0,0,4],[7,6,5]]  <strong>输出：</strong>6  <strong>解释：</strong>沿着上面的路径，你可以用 6 步，按从最矮到最高的顺序砍掉这些树。</pre><p><strong>示例 2：</strong></p>  <img alt="" src="https://assets.leetcode.com/uploads/2020/11/26/trees2.jpg" style="width: 242px; height: 242px;" />  <pre>  <strong>输入：</strong>forest = [[1,2,3],[0,0,0],[7,6,5]]  <strong>输出：</strong>-1  <strong>解释：</strong>由于中间一行被障碍阻塞，无法访问最下面一行中的树。  </pre><p><strong>示例 3：</strong></p><pre>  <strong>输入：</strong>forest = [[2,3,4],[0,0,5],[8,7,6]]  <strong>输出：</strong>6  <strong>解释：</strong>可以按与示例 1 相同的路径来砍掉所有的树。  (0,0) 位置的树，可以直接砍去，不用算步数。  </pre><p> </p><p><strong>提示：</strong></p><ul>     <li><code>m == forest.length</code></li>     <li><code>n == forest[i].length</code></li>     <li><code>1 <= m, n <= 50</code></li>     <li><code>0 <= forest[i][j] <= 10<sup>9</sup></code></li>  </ul>  <div><div>Related Topics</div><div><li>广度优先搜索</li><li>数组</li><li>矩阵</li><li>堆（优先队列）</li></div></div><h1 id="思路">思路</h1><ol><li><p>记录每颗需要砍树的位置，并排好序</p><p>注意：这个需要砍的树是从<font color="red">2</font>开始算的，不是<font color="red">1</font></p></li><li><p>循环计算到达下一棵被砍树的步数</p><p>可使用广度优先搜索，从出发的树开始，依次取出并将下一步能够到达的树加入到队列，直到目标树为止</p></li></ol><h1 id="代码">代码</h1><p>java：</p><pre><code class="language-java">class Solution {    public int cutOffTree(List&lt;List&lt;Integer&gt;&gt; forest) {        /*        起始位置不可到达的情况，即坐标（0,0）位置为0         */        if (forest.get(0).get(0) == 0) {            return -1;        }        int xL = forest.size();        int yL = forest.get(0).size();        /*        按照顺序排列需要砍的树，记录每棵树的位置         */        TreeMap&lt;Integer, Pair&lt;Integer, Integer&gt;&gt; map = new TreeMap&lt;&gt;();        for (int i = 0; i &lt; xL; i++) {            List&lt;Integer&gt; list = forest.get(i);            for (int j = 0; j &lt; yL; j++) {                if (list.get(j) &gt; 1) {                    map.put(list.get(j), new Pair&lt;&gt;(i, j));                }            }        }        int step = 0;        Pair&lt;Integer, Integer&gt; pair = null;        Queue&lt;Pair&lt;Integer, Integer&gt;&gt; queue = new LinkedList&lt;&gt;();        queue.add(new Pair&lt;&gt;(0, 0));        boolean[][] uses = new boolean[xL][yL];        uses[0][0] = true;        int[] xs = new int[]{1, -1, 0, 0};        int[] ys = new int[]{0, 0, 1, -1};        for (int key : map.keySet()) {            Pair&lt;Integer, Integer&gt; cur = map.get(key);            if (queue.peek().equals(cur)) {                continue;            }            boolean bl = false;            /*            计算到达下一棵需要砍树的步数             */            while (!queue.isEmpty() &amp;&amp; !bl) {                int nums = queue.size();                step++;                for (int i = 0; i &lt; nums &amp;&amp; !bl; i++) {                    Pair&lt;Integer, Integer&gt; tmp = queue.poll();                    for (int j = 0; j &lt; 4; j++) {                        int x = tmp.getKey() + xs[j];                        int y = tmp.getValue() + ys[j];                        if (x == cur.getKey() &amp;&amp; y == cur.getValue()) {                            bl = true;                            break;                        }                        if (x &lt; 0 || x &gt;= xL || y &lt; 0 || y                                &gt;= yL || uses[x][y] || forest.get(x).get(y                            continue;                        }                        queue.add(new Pair&lt;&gt;(x, y));                        uses[x][y] = true;                    }                }            }            if (!bl) {                return -1;            }            queue = new LinkedList&lt;&gt;();            queue.add(cur);            uses = new boolean[xL][yL];            uses[cur.getKey()][cur.getValue()] = true;        }        return step;    }}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 24 May 2022 09:18:23 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣周赛293题解]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣周赛293题解</link>
                    <description>
                            <![CDATA[<h1 id="第一题">第一题</h1><h2 id="力扣原题链接">力扣原题链接：</h2><p><a href="https://leetcode.cn/problems/find-resultant-array-after-removing-anagrams/">2273. 移除字母异位词后的结果数组</a></p><h2 id="单个题解">单个题解：</h2><p><a href="http://192.168.0.198:5080/post/find-resultant-array-after-removing-anagrams/">力扣2273. 移除字母异位词后的结果数组</a></p><h2 id="题目">题目：</h2><p>给你一个下标从 <strong>0</strong> 开始的字符串 <code>words</code> ，其中 <code>words[i]</code> 由小写英文字符组成。</p><p>在一步操作中，需要选出任一下标 <code>i</code> ，从 <code>words</code> 中 <strong>删除</strong> <code>words[i]</code> 。其中下标 <code>i</code> 需要同时满足下述两个条件：</p><ol>       <li><code>0 < i < words.length</code></li>       <li><code>words[i - 1]</code> 和 <code>words[i]</code> 是 <strong>字母异位词</strong> 。</li>    </ol><p>只要可以选出满足条件的下标，就一直执行这个操作。</p><p>在执行所有操作后，返回 <code>words</code> 。可以证明，按任意顺序为每步操作选择下标都会得到相同的结果。</p><p><strong>字母异位词</strong> 是由重新排列源单词的字母得到的一个新单词，所有源单词中的字母通常恰好只用一次。例如，<code>"dacb"</code> 是 <code>"abdc"</code> 的一个字母异位词。</p><p> </p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>words = ["abba","baba","bbaa","cd","cd"]    <strong>输出：</strong>["abba","cd"]    <strong>解释：</strong>    获取结果数组的方法之一是执行下述步骤：  - 由于 words[2] = "bbaa" 和 words[1] = "baba" 是字母异位词，选择下标 2 并删除 words[2] 。      现在 words = ["abba","baba","cd","cd"] 。  - 由于 words[1] = "baba" 和 words[0] = "abba" 是字母异位词，选择下标 1 并删除 words[1] 。      现在 words = ["abba","cd","cd"] 。  - 由于 words[2] = "cd" 和 words[1] = "cd" 是字母异位词，选择下标 2 并删除 words[2] 。      现在 words = ["abba","cd"] 。  无法再执行任何操作，所以 ["abba","cd"] 是最终答案。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>words = ["a","b","c","d","e"]    <strong>输出：</strong>["a","b","c","d","e"]    <strong>解释：</strong>    words 中不存在互为字母异位词的两个相邻字符串，所以无需执行任何操作。</pre><p> </p><p><strong>提示：</strong></p><ul>       <li><code>1 <= words.length <= 100</code></li>       <li><code>1 <= words[i].length <= 10</code></li>       <li><code>words[i]</code> 由小写英文字母组成</li>    </ul>    <div><div>Related Topics</div><div><li>数组</li><li>哈希表</li><li>字符串</li><li>排序</li></div></div><h2 id="思路">思路：</h2><p>遍历字符串数组，分别将每一个字符串装换成字符数组，字符数组排序，如果排序后转成的字符串一样，则说明是字母异位词</p><h2 id="代码">代码：</h2><p>java：</p><pre><code class="language-java">class Solution {    public List&lt;String&gt; removeAnagrams(String[] words) {        char[] strs = words[0].toCharArray();        Arrays.sort(strs);        List&lt;String&gt; list = new ArrayList&lt;&gt;();        int index = 0;        for (int i = 1; i &lt; words.length; i++) {            char[] strs1 = words[i].toCharArray();            Arrays.sort(strs1);            if (!String.valueOf(strs).equals(String.valueOf(strs1))) {                list.add(words[index]);                strs = strs1;                index = i;            }        }        list.add(words[index]);        return list;    }}</code></pre><h1 id="第二题">第二题</h1><h2 id="力扣原题链接-1">力扣原题链接：</h2><p><a href="https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors/">2274. 不含特殊楼层的最大连续楼层数</a></p><h2 id="单个题解-1">单个题解：</h2><p><a href="http://192.168.0.198:5080/post/maximum-consecutive-floors-without-special-floors/">力扣2274. 不含特殊楼层的最大连续楼层数</a></p><h2 id="题目-1">题目：</h2><p>Alice 管理着一家公司，并租用大楼的部分楼层作为办公空间。Alice 决定将一些楼层作为 <strong>特殊楼层</strong> ，仅用于放松。</p><p>给你两个整数 <code>bottom</code> 和 <code>top</code> ，表示 Alice 租用了从 <code>bottom</code> 到 <code>top</code>（含 <code>bottom</code> 和 <code>top</code> 在内）的所有楼层。另给你一个整数数组 <code>special</code> ，其中 <code>special[i]</code> 表示  Alice 指定用于放松的特殊楼层。</p><p>返回不含特殊楼层的 <strong>最大</strong> 连续楼层数。</p><p> </p><p><strong>示例 1：</strong></p><pre>  <strong>输入：</strong>bottom = 2, top = 9, special = [4,6]  <strong>输出：</strong>3  <strong>解释：</strong>下面列出的是不含特殊楼层的连续楼层范围：  - (2, 3) ，楼层数为 2 。  - (5, 5) ，楼层数为 1 。  - (7, 9) ，楼层数为 3 。  因此，返回最大连续楼层数 3 。  </pre><p><strong>示例 2：</strong></p><pre>  <strong>输入：</strong>bottom = 6, top = 8, special = [7,6,8]  <strong>输出：</strong>0  <strong>解释：</strong>每层楼都被规划为特殊楼层，所以返回 0 。  </pre><p> </p><p><strong>提示</strong></p><ul>     <li><code>1 <= special.length <= 10<sup>5</sup></code></li>     <li><code>1 <= bottom <= special[i] <= top <= 10<sup>9</sup></code></li>     <li><code>special</code> 中的所有值 <strong>互不相同</strong></li>  </ul>  <div><div>Related Topics</div><div><li>数组</li><li>排序</li></div></div><h2 id="思路-1">思路：</h2><p>这题相当于在bottom到top的范围内，被special的数分割了，我们需要找到分割后最长的一段</p><p>步骤：</p><ol><li>为了保证数据的顺序进行，对special进行排序</li><li>遍历<code>special</code>对<code>bottom~top</code>进行分割，当<code>bottom&lt;=special[i]</code>时，<br />连续楼层数为<code>special[i]-bottom</code>，与之前的最大连续层数对比，得到当前的最大连续层数，<br />同时更新<code>bottom = special[i] + 1</code></li><li>遍历完，还有最后一段的连续层数<code>top - special[special.length - 1]</code></li><li>至此，不包含特殊层的最大的连续层数就出来了</li></ol><h2 id="代码-1">代码：</h2><p>java：</p><pre><code class="language-java">class Solution {    public int maxConsecutive(int bottom, int top, int[] special) {        Arrays.sort(special);        int max = 0;        for (int j : special) {            if (bottom &lt;= j) {                max = Math.max(max, j - bottom);                bottom = j + 1;            }        }        max = Math.max(max, top - special[special.length - 1]);        return max;    }}</code></pre><h1 id="第三题">第三题</h1><h2 id="力扣原题链接-2">力扣原题链接：</h2><p><a href="https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero/">2275. 按位与结果大于零的最长组合</a></p><h2 id="单个题解-2">单个题解：</h2><p><a href="http://192.168.0.198:5080/post/largest-combination-with-bitwise-and-greater-than-zero/">力扣2275. 按位与结果大于零的最长组合</a></p><h2 id="题目-2">题目：</h2><p>对数组 <code>nums</code> 执行 <strong>按位与</strong> 相当于对数组 <code>nums</code> 中的所有整数执行 <strong>按位与</strong> 。</p><ul>     <li>例如，对 <code>nums = [1, 5, 3]</code> 来说，按位与等于 <code>1 & 5 & 3 = 1</code> 。</li>     <li>同样，对 <code>nums = [7]</code> 而言，按位与等于 <code>7</code> 。</li>  </ul><p>给你一个正整数数组 <code>candidates</code> 。计算 <code>candidates</code> 中的数字每种组合下 <strong>按位与</strong> 的结果。 <code>candidates</code> 中的每个数字在每种组合中只能使用 <strong>一次</strong> 。</p><p>返回按位与结果大于 <code>0</code> 的 <strong>最长</strong> 组合的长度<em>。</em></p><p> </p><p><strong>示例 1：</strong></p><pre>  <strong>输入：</strong>candidates = [16,17,71,62,12,24,14]  <strong>输出：</strong>4  <strong>解释：</strong>组合 [16,17,62,24] 的按位与结果是 16 & 17 & 62 & 24 = 16 > 0 。  组合长度是 4 。  可以证明不存在按位与结果大于 0 且长度大于 4 的组合。  注意，符合长度最大的组合可能不止一种。  例如，组合 [62,12,24,14] 的按位与结果是 62 & 12 & 24 & 14 = 8 > 0 。  </pre><p><strong>示例 2：</strong></p><pre>  <strong>输入：</strong>candidates = [8,8]  <strong>输出：</strong>2  <strong>解释：</strong>最长组合是 [8,8] ，按位与结果 8 & 8 = 8 > 0 。  组合长度是 2 ，所以返回 2 。  </pre><p> </p><p><strong>提示：</strong></p><ul>     <li><code>1 <= candidates.length <= 10<sup>5</sup></code></li>     <li><code>1 <= candidates[i] <= 10<sup>7</sup></code></li>  </ul>  <div><div>Related Topics</div><div><li>位运算</li><li>数组</li><li>哈希表</li><li>计数</li></div></div><h2 id="思路-2">思路：</h2><p>这题需要找出按位与结果大于0的最长组合的长度，按位与结果大于0，<br />说明这个数组中的每一个二进制数都有相同的一位是1，根据这题给的数组值的范围，<br />可以确定最多有24位，那么我们可以循环24次数组，每一次循环统计出第<code>i</code>位位数为1的个数，<br />然后将每一次的个数做比较，得出最长组合的长度</p><h2 id="代码-2">代码：</h2><p>java：</p><pre><code class="language-java">class Solution {    public int largestCombination(int[] candidates) {        int max = 0;        for (int i = 0; i &lt; 25; i++) {            int cnt = 0;            for (int j = 0; j &lt; candidates.length; j++) {                if ((candidates[j] &amp; (1 &lt;&lt; i)) &gt; 0) {                    cnt++;                }            }            max = Math.max(max, cnt);        }        return max;    }}</code></pre><h1 id="第四题">第四题</h1><h2 id="力扣原题链接-3">力扣原题链接：</h2><p><a href="https://leetcode.cn/problems/count-integers-in-intervals/">2276. 统计区间中的整数数目</a></p><h2 id="单个题解-3">单个题解：</h2><p><a href="http://192.168.0.198:5080/post/count-integers-in-intervals/">力扣2276. 统计区间中的整数数目</a></p><h2 id="题目-3">题目：</h2><p>给你区间的 <strong>空</strong> 集，请你设计并实现满足要求的数据结构：</p><ul>     <li><strong>新增：</strong>添加一个区间到这个区间集合中。</li>     <li><strong>统计：</strong>计算出现在 <strong>至少一个</strong> 区间中的整数个数。</li>  </ul><p>实现 <code>CountIntervals</code> 类：</p><ul>     <li><code>CountIntervals()</code> 使用区间的空集初始化对象</li>     <li><code>void add(int left, int right)</code> 添加区间 <code>[left, right]</code> 到区间集合之中。</li>     <li><code>int count()</code> 返回出现在 <strong>至少一个</strong> 区间中的整数个数。</li>  </ul><p><strong>注意：</strong>区间 <code>[left, right]</code> 表示满足 <code>left <= x <= right</code> 的所有整数 <code>x</code> 。</p><p> </p><p><strong>示例 1：</strong></p><pre>  <strong>输入</strong>  ["CountIntervals", "add", "add", "count", "add", "count"]  [[], [2, 3], [7, 10], [], [5, 8], []]  <strong>输出</strong>  [null, null, null, 6, null, 8]  <strong>解释</strong>  CountIntervals countIntervals = new CountIntervals(); // 用一个区间空集初始化对象  countIntervals.add(2, 3);  // 将 [2, 3] 添加到区间集合中  countIntervals.add(7, 10); // 将 [7, 10] 添加到区间集合中  countIntervals.count();    // 返回 6                             // 整数 2 和 3 出现在区间 [2, 3] 中                             // 整数 7、8、9、10 出现在区间 [7, 10] 中  countIntervals.add(5, 8);  // 将 [5, 8] 添加到区间集合中  countIntervals.count();    // 返回 8                             // 整数 2 和 3 出现在区间 [2, 3] 中                             // 整数 5 和 6 出现在区间 [5, 8] 中                             // 整数 7 和 8 出现在区间 [5, 8] 和区间 [7, 10] 中                             // 整数 9 和 10 出现在区间 [7, 10] 中</pre><p> </p><p><strong>提示：</strong></p><ul>     <li><code>1 <= left <= right <= 10<sup>9</sup></code></li>     <li>最多调用  <code>add</code> 和 <code>count</code> 方法 <strong>总计</strong> <code>10<sup>5</sup></code> 次</li>     <li>调用 <code>count</code> 方法至少一次</li>  </ul><h2 id="思路-3">思路：</h2><p>这题我的思路是添加一次整理一次并同时计数，利用java的TreeSet结构，可以快速的定位数据。<br />典型的模板题</p><h2 id="代码-3">代码：</h2><p>java：</p><pre><code class="language-java">class CountIntervals {    TreeSet&lt;Interval&gt; ranges;    int cnt;    public CountIntervals() {        ranges = new TreeSet();        cnt = 0;    }    public void add(int left, int right) {        Iterator&lt;Interval&gt; itr = ranges.tailSet(new Interval(0, left - 1)).iterator();        while (itr.hasNext()) {            Interval iv = itr.next();            if (right &lt; iv.left) {                break;            }            left = Math.min(left, iv.left);            right = Math.max(right, iv.right);            cnt -= iv.right - iv.left + 1;            itr.remove();        }        ranges.add(new Interval(left, right));        cnt += right - left + 1;    }    public int count() {        return cnt;    }}public class Interval implements Comparable&lt;Interval&gt; {    int left;    int right;    public Interval(int left, int right) {        this.left = left;        this.right = right;    }    public int compareTo(Interval that) {        if (this.right == that.right) return this.left - that.left;        return this.right - that.right;    }}</code></pre>]]>
                    </description>
                    <pubDate>Thu, 19 May 2022 14:52:09 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2276. 统计区间中的整数数目]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2276统计区间中的整数数目</link>
                    <description>
                            <![CDATA[<p>力扣周赛293--第四题</p><p><a href="https://leetcode.cn/problems/count-integers-in-intervals/">2276. 统计区间中的整数数目</a></p><h1 id="题目">题目</h1><p>给你区间的 <strong>空</strong> 集，请你设计并实现满足要求的数据结构：</p><ul><li><strong>新增：</strong>添加一个区间到这个区间集合中。</li><li><strong>统计：</strong>计算出现在 <strong>至少一个</strong> 区间中的整数个数。</li></ul><p>实现 <code>CountIntervals</code> 类：</p><ul><li><code>CountIntervals()</code> 使用区间的空集初始化对象</li><li><code>void add(int left, int right)</code> 添加区间 <code>[left, right]</code> 到区间集合之中。</li><li><code>int count()</code> 返回出现在 <strong>至少一个</strong> 区间中的整数个数。</li></ul><p><strong>注意：</strong>区间 <code>[left, right]</code> 表示满足 <code>left &lt;= x &lt;= right</code> 的所有整数 <code>x</code> 。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><pre><strong>输入</strong>["CountIntervals", "add", "add", "count", "add", "count"][[], [2, 3], [7, 10], [], [5, 8], []]<strong>输出</strong>[null, null, null, 6, null, 8]<strong>解释</strong>CountIntervals countIntervals = new CountIntervals(); // 用一个区间空集初始化对象countIntervals.add(2, 3);  // 将 [2, 3] 添加到区间集合中countIntervals.add(7, 10); // 将 [7, 10] 添加到区间集合中countIntervals.count();    // 返回 6                           // 整数 2 和 3 出现在区间 [2, 3] 中                           // 整数 7、8、9、10 出现在区间 [7, 10] 中countIntervals.add(5, 8);  // 将 [5, 8] 添加到区间集合中countIntervals.count();    // 返回 8                           // 整数 2 和 3 出现在区间 [2, 3] 中                           // 整数 5 和 6 出现在区间 [5, 8] 中                           // 整数 7 和 8 出现在区间 [5, 8] 和区间 [7, 10] 中                           // 整数 9 和 10 出现在区间 [7, 10] 中</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= left &lt;= right &lt;= 10<sup>9</sup></code></li><li>最多调用&nbsp; <code>add</code> 和 <code>count</code> 方法 <strong>总计</strong> <code>10<sup>5</sup></code> 次</li><li>调用 <code>count</code> 方法至少一次</li></ul><h1 id="思路">思路</h1><p>这题我的思路是添加一次整理一次并同时计数，利用java的TreeSet结构，可以快速的定位数据。<br />典型的模板题</p><h1 id="代码">代码</h1><p>java：</p><pre><code class="language-java">class CountIntervals {    TreeSet&lt;Interval&gt; ranges;    int cnt;    public CountIntervals() {        ranges = new TreeSet();        cnt = 0;    }    public void add(int left, int right) {        Iterator&lt;Interval&gt; itr = ranges.tailSet(new Interval(0, left - 1)).iterator();        while (itr.hasNext()) {            Interval iv = itr.next();            if (right &lt; iv.left) {                break;            }            left = Math.min(left, iv.left);            right = Math.max(right, iv.right);            cnt -= iv.right - iv.left + 1;            itr.remove();        }        ranges.add(new Interval(left, right));        cnt += right - left + 1;    }    public int count() {        return cnt;    }}public class Interval implements Comparable&lt;Interval&gt; {    int left;    int right;    public Interval(int left, int right) {        this.left = left;        this.right = right;    }    public int compareTo(Interval that) {        if (this.right == that.right) return this.left - that.left;        return this.right - that.right;    }}</code></pre>]]>
                    </description>
                    <pubDate>Thu, 19 May 2022 13:54:33 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2275. 按位与结果大于零的最长组合]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2275按位与结果大于零的最长组合</link>
                    <description>
                            <![CDATA[<p>力扣周赛293--第三题</p><p><a href="https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero/">2275. 按位与结果大于零的最长组合</a></p><h1 id="题目">题目</h1><p>对数组&nbsp;<code>nums</code> 执行 <strong>按位与</strong> 相当于对数组&nbsp;<code>nums</code> 中的所有整数执行 <strong>按位与</strong> 。</p><ul><li>例如，对 <code>nums = [1, 5, 3]</code> 来说，按位与等于 <code>1 &amp; 5 &amp; 3 = 1</code> 。</li><li>同样，对 <code>nums = [7]</code> 而言，按位与等于 <code>7</code> 。</li></ul><p>给你一个正整数数组 <code>candidates</code> 。计算 <code>candidates</code> 中的数字每种组合下 <strong>按位与</strong> 的结果。 <code>candidates</code> 中的每个数字在每种组合中只能使用 <strong>一次</strong> 。</p><p>返回按位与结果大于 <code>0</code> 的 <strong>最长</strong> 组合的长度<em>。</em></p><p>&nbsp;</p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>candidates = [16,17,71,62,12,24,14]<strong>输出：</strong>4<strong>解释：</strong>组合 [16,17,62,24] 的按位与结果是 16 &amp; 17 &amp; 62 &amp; 24 = 16 &gt; 0 。组合长度是 4 。可以证明不存在按位与结果大于 0 且长度大于 4 的组合。注意，符合长度最大的组合可能不止一种。例如，组合 [62,12,24,14] 的按位与结果是 62 &amp; 12 &amp; 24 &amp; 14 = 8 &gt; 0 。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>candidates = [8,8]<strong>输出：</strong>2<strong>解释：</strong>最长组合是 [8,8] ，按位与结果 8 &amp; 8 = 8 &gt; 0 。组合长度是 2 ，所以返回 2 。</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= candidates.length &lt;= 10<sup>5</sup></code></li><li><code>1 &lt;= candidates[i] &lt;= 10<sup>7</sup></code></li></ul><div><div>Related Topics</div><div><li>位运算</li><li>数组</li><li>哈希表</li><li>计数</li></div></div><h1 id="思路">思路</h1><p>这题需要找出按位与结果大于0的最长组合的长度，按位与结果大于0，<br />说明这个数组中的每一个二进制数都有相同的一位是1，根据这题给的数组值的范围，<br />可以确定最多有24位，那么我们可以循环24次数组，每一次循环统计出第<code>i</code>位位数为1的个数，<br />然后将每一次的个数做比较，得出最长组合的长度</p><h1 id="代码">代码</h1><p>java：</p><pre><code class="language-java">class Solution {    public int largestCombination(int[] candidates) {        int max = 0;        for (int i = 0; i &lt; 25; i++) {            int cnt = 0;            for (int j = 0; j &lt; candidates.length; j++) {                if ((candidates[j] &amp; (1 &lt;&lt; i)) &gt; 0) {                    cnt++;                }            }            max = Math.max(max, cnt);        }        return max;    }}</code></pre>]]>
                    </description>
                    <pubDate>Thu, 19 May 2022 13:41:13 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2274. 不含特殊楼层的最大连续楼层数]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2274不含特殊楼层的最大连续楼层数</link>
                    <description>
                            <![CDATA[<p>力扣周赛293--第二题</p><p><a href="https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors/">2274. 不含特殊楼层的最大连续楼层数</a></p><h1 id="题目">题目</h1><p>Alice 管理着一家公司，并租用大楼的部分楼层作为办公空间。Alice 决定将一些楼层作为 <strong>特殊楼层</strong> ，仅用于放松。</p><p>给你两个整数 <code>bottom</code> 和 <code>top</code> ，表示 Alice 租用了从 <code>bottom</code> 到 <code>top</code>（含 <code>bottom</code> 和 <code>top</code> 在内）的所有楼层。另给你一个整数数组 <code>special</code> ，其中 <code>special[i]</code> 表示&nbsp; Alice 指定用于放松的特殊楼层。</p><p>返回不含特殊楼层的 <strong>最大</strong> 连续楼层数。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>bottom = 2, top = 9, special = [4,6]<strong>输出：</strong>3<strong>解释：</strong>下面列出的是不含特殊楼层的连续楼层范围：- (2, 3) ，楼层数为 2 。- (5, 5) ，楼层数为 1 。- (7, 9) ，楼层数为 3 。因此，返回最大连续楼层数 3 。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>bottom = 6, top = 8, special = [7,6,8]<strong>输出：</strong>0<strong>解释：</strong>每层楼都被规划为特殊楼层，所以返回 0 。</pre><p>&nbsp;</p><p><strong>提示</strong></p><ul><li><code>1 &lt;= special.length &lt;= 10<sup>5</sup></code></li><li><code>1 &lt;= bottom &lt;= special[i] &lt;= top &lt;= 10<sup>9</sup></code></li><li><code>special</code> 中的所有值 <strong>互不相同</strong></li></ul><div><div>Related Topics</div><div><li>数组</li><li>排序</li></div></div><h1 id="思路">思路</h1><p>这题相当于在bottom到top的范围内，被special的数分割了，我们需要找到分割后最长的一段</p><p>步骤：</p><ol><li>为了保证数据的顺序进行，对special进行排序</li><li>遍历<code>special</code>对<code>bottom~top</code>进行分割，当<code>bottom&lt;=special[i]</code>时，<br />连续楼层数为<code>special[i]-bottom</code>，与之前的最大连续层数对比，得到当前的最大连续层数，<br />同时更新<code>bottom = special[i] + 1</code></li><li>遍历完，还有最后一段的连续层数<code>top - special[special.length - 1]</code></li><li>至此，不包含特殊层的最大的连续层数就出来了</li></ol><h1 id="代码">代码</h1><p>java：</p><pre><code class="language-java">class Solution {    public int maxConsecutive(int bottom, int top, int[] special) {        Arrays.sort(special);        int max = 0;        for (int j : special) {            if (bottom &lt;= j) {                max = Math.max(max, j - bottom);                bottom = j + 1;            }        }        max = Math.max(max, top - special[special.length - 1]);        return max;    }}</code></pre>]]>
                    </description>
                    <pubDate>Thu, 19 May 2022 09:57:58 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2273. 移除字母异位词后的结果数组]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2273移除字母异位词后的结果数组</link>
                    <description>
                            <![CDATA[<p>力扣周赛293--第一题</p><p><a href="https://leetcode.cn/problems/find-resultant-array-after-removing-anagrams/">2273. 移除字母异位词后的结果数组</a></p><h1 id="题目">题目</h1><p>给你一个下标从 <strong>0</strong> 开始的字符串 <code>words</code> ，其中 <code>words[i]</code> 由小写英文字符组成。</p><p>在一步操作中，需要选出任一下标 <code>i</code> ，从 <code>words</code> 中 <strong>删除</strong> <code>words[i]</code> 。其中下标 <code>i</code> 需要同时满足下述两个条件：</p><ol>     <li><code>0 < i < words.length</code></li>     <li><code>words[i - 1]</code> 和 <code>words[i]</code> 是 <strong>字母异位词</strong> 。</li>  </ol><p>只要可以选出满足条件的下标，就一直执行这个操作。</p><p>在执行所有操作后，返回 <code>words</code> 。可以证明，按任意顺序为每步操作选择下标都会得到相同的结果。</p><p><strong>字母异位词</strong> 是由重新排列源单词的字母得到的一个新单词，所有源单词中的字母通常恰好只用一次。例如，<code>"dacb"</code> 是 <code>"abdc"</code> 的一个字母异位词。</p><p> </p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>words = ["abba","baba","bbaa","cd","cd"]  <strong>输出：</strong>["abba","cd"]  <strong>解释：</strong>  获取结果数组的方法之一是执行下述步骤：  - 由于 words[2] = "bbaa" 和 words[1] = "baba" 是字母异位词，选择下标 2 并删除 words[2] 。    现在 words = ["abba","baba","cd","cd"] 。  - 由于 words[1] = "baba" 和 words[0] = "abba" 是字母异位词，选择下标 1 并删除 words[1] 。    现在 words = ["abba","cd","cd"] 。  - 由于 words[2] = "cd" 和 words[1] = "cd" 是字母异位词，选择下标 2 并删除 words[2] 。    现在 words = ["abba","cd"] 。  无法再执行任何操作，所以 ["abba","cd"] 是最终答案。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>words = ["a","b","c","d","e"]  <strong>输出：</strong>["a","b","c","d","e"]  <strong>解释：</strong>  words 中不存在互为字母异位词的两个相邻字符串，所以无需执行任何操作。</pre><p> </p><p><strong>提示：</strong></p><ul>     <li><code>1 <= words.length <= 100</code></li>     <li><code>1 <= words[i].length <= 10</code></li>     <li><code>words[i]</code> 由小写英文字母组成</li>  </ul>  <div><div>Related Topics</div><div><li>数组</li><li>哈希表</li><li>字符串</li><li>排序</li></div></div><h1 id="思路">思路</h1><p>遍历字符串数组，分别将每一个字符串装换成字符数组，字符数组排序，如果排序后转成的字符串一样，则说明是字母异位词</p><h1 id="代码">代码</h1><p>java：</p><pre><code class="language-java">class Solution {    public List&lt;String&gt; removeAnagrams(String[] words) {        char[] strs = words[0].toCharArray();        Arrays.sort(strs);        List&lt;String&gt; list = new ArrayList&lt;&gt;();        int index = 0;        for (int i = 1; i &lt; words.length; i++) {            char[] strs1 = words[i].toCharArray();            Arrays.sort(strs1);            if (!String.valueOf(strs).equals(String.valueOf(strs1))) {                list.add(words[index]);                strs = strs1;                index = i;            }        }        list.add(words[index]);        return list;    }}</code></pre>]]>
                    </description>
                    <pubDate>Thu, 19 May 2022 09:27:33 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣周赛292题解]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣周赛292题解</link>
                    <description>
                            <![CDATA[<h1 id="第一题">第一题</h1><h2 id="力扣原题链接">力扣原题链接：</h2><p><a href="https://leetcode.cn/problems/largest-3-same-digit-number-in-string/">2264. 字符串中最大的 3 位相同数字</a></p><h2 id="单个题解">单个题解：</h2><p><a href="http://192.168.0.198:5080/post/largest-3-same-digit-number-in-string/">力扣2264. 字符串中最大的 3 位相同数字</a></p><h2 id="题解">题解：</h2><p>这题是要找最大的3个相同数并且3个数是相连的，因为数字的话只有0~9这10个数字，找最大的，那我就从999开始，然后依次888、777。。。000，只要字符串中存在，那就是它了。</p><h2 id="java代码">java代码：</h2><pre><code class="language-java">public String largestGoodInteger(String num) {    String str;    for (int i = 9; i &gt;= 0; i--) {        str = &quot;&quot; + i + i + i;        if (num.contains(str)) {            return str;        }    }    return &quot;&quot;;}</code></pre><h1 id="第二题">第二题</h1><h2 id="力扣原题链接-1">力扣原题链接：</h2><p><a href="https://leetcode.cn/problems/count-nodes-equal-to-average-of-subtree/">6057. 统计值等于子树平均值的节点数</a></p><h2 id="单个题解-1">单个题解：</h2><p><a href="http://192.168.0.198:5080/post/count-nodes-equal-to-average-of-subtree/">力扣6057. 统计值等于子树平均值的节点数</a></p><h2 id="题解-1">题解：</h2><p>这题的思路：</p><ul><li><p>先深度遍历树，统计出每个节点包含的节点数，并将其放入队列中</p></li><li><p>再深度遍历一次树，这次计算出每个节点的元素和，并从队列中取到该节点的节点数，然后求平均值做判断</p></li></ul><h2 id="java代码-1">java代码：</h2><pre><code class="language-java">class Solution {    public int averageOfSubtree(TreeNode root) {        counts(root);        sums(root);        return count;    }    Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();    int count = 0;    private int counts(TreeNode root) {        if (root == null) {            return 0;        }        int cnt = counts(root.left) + counts(root.right) + 1;        queue.add(cnt);        return cnt;    }    private int sums(TreeNode root) {        if (root == null) {            return 0;        }        int sum = root.val;        sum += sums(root.left);        sum += sums(root.right);        if (sum / queue.poll() == root.val) {            count++;        }        return sum;    }}</code></pre><h1 id="第三题">第三题</h1><h2 id="力扣原题链接-2">力扣原题链接：</h2><p><a href="https://leetcode.cn/problems/count-number-of-texts/">2266. 统计打字方案数</a></p><h2 id="单个题解-2">单个题解：</h2><p><a href="http://192.168.0.198:5080/post/check-if-there-is-a-valid-parentheses-string-path/">力扣2267. 检查是否有合法括号字符串路径</a></p><h2 id="题解-2">题解：</h2><p>这题标的是中等题，个人觉得解题方法有点取巧，怎么取巧尼，因为重复的数最多4个，我完全可以嵌套3层if来处理，当然我也是这么干的。只要遍历一遍就可以了。</p><p>在遍历到索引<code>i</code>时，有如下情况：</p><ol><li><p>当前数字不和前面的组合，自己单独成一个新的</p><p>索引<code>i</code>的种数 = 索引<code>i-1</code>的种数</p></li><li><p>当前数字与前一个相等，那么该数字的组合就有两种情况</p><ul><li><p>直接和前一个数字凑一起</p><p>索引<code>i</code>的种数=索引<code>i-2</code>的种数</p></li><li><p>索引<code>i</code>的数字和索引<code>i-2</code>的数字也相等，这也有2种情况</p><ul><li><p>把索引<code>i</code>,<code>i-1</code>,<code>i-2</code>都凑一起</p><p>索引<code>i</code>的种数=索引<code>i-3</code>的种数</p></li><li><p>索引<code>i</code>是7或者9，最多可以连续4个，把索引<code>i</code>,<code>i-1</code>,<code>i-2</code>,<code>i-3</code>都凑一起</p><p>索引<code>i</code>的种数=索引<code>i-4</code>的种数</p></li></ul></li></ul></li></ol><p>同时，为了保证数据没有超过int的最大值，这里对于每一次的结果都对109+7取余</p><h2 id="java代码-2">java代码：</h2><pre><code class="language-java">class Solution {    public int countTexts(String pressedKeys) {        int[] cnts = new int[pressedKeys.length() + 1];        cnts[0] = 1;        cnts[1] = 1;        int mod = 1000000007;        for (int i = 1; i &lt; pressedKeys.length(); i++) {            cnts[i + 1] = cnts[i];            if (pressdKeys.charAt(i) == pressedKeys.charAt(i - 1)) {                cnts[i + 1] += cnts[i - 1];                cnts[i + 1] %= mod;                if (i &gt; 1 &amp;&amp; pressedKeys.charAt(i) == pressedKeys.charAt(i - 2)) {                    cnts[i + 1] += cnts[i - 2];                    cnts[i + 1] %= mod;                    if (i &gt; 2 &amp;&amp; pressedKeys.charAt(i) == pressedKeys.charAt(i - 3) &amp;&amp; (pressedKeys.charAt(i) == '7' || pressedKeys.charAt(i) == '                        cnts[i + 1] += cnts[i - 3];                        cnts[i + 1] %= mod;                    }                }            }        }        return cnts[pressedKeys.length()];    }}</code></pre><h1 id="第四题">第四题</h1><h2 id="力扣原题链接-3">力扣原题链接：</h2><p><a href="https://leetcode.cn/problems/check-if-there-is-a-valid-parentheses-string-path/">2267. 检查是否有合法括号字符串路径</a></p><h2 id="单个题解-3">单个题解：</h2><p><a href="http://192.168.0.198:5080/post/count-number-of-texts/">力扣2266. 统计打字方案数</a></p><h2 id="题解-3">题解：</h2><p>从左上角到右下角，依次路过，下一个坐标一定是该坐标的右侧或者下侧的坐标，同时玩吗记录下路过到该坐标时未配对的’<code>'('</code>的个数，如果是负数则这条路不对旧不用继续下去了。然后一直到右下角的时候，如果个数为1，则满足条件</p><h2 id="java代码-3">java代码：</h2><pre><code class="language-java">class Solution {    public boolean hasValidPath(char[][] grid) {        xl = grid.length;        yl = grid[0].length;        use = new boolean[xl][yl][xl * yl];        if ((xl + yl) % 2 == 0 || grid[0][0] == ')' || grid[xl - 1][yl -             return false;        }        dfs(grid, 0, 0, 0);        return bl;    }    int xl;    int yl;    boolean bl = false;    boolean[][][] use;    private void dfs(char[][] grid, int x, int y, int cnt) {        if (x &gt;= xl || y &gt;= yl || cnt &gt; xl - x + yl - y - 1) {            return;        }        if (x == xl - 1 &amp;&amp; y == yl - 1) {            bl = cnt == 1;        }        if (use[x][y][cnt]) {            return;        }        use[x][y][cnt] = true;        cnt += grid[x][y] == '(' ? 1 : -1;        if (cnt &lt; 0) {            return;        }        if (!bl) {            dfs(grid, x + 1, y, cnt);        }        if (!bl) {            dfs(grid, x, y + 1, cnt);        }    }}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 10 May 2022 14:01:37 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2267. 检查是否有合法括号字符串路径]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2267检查是否有合法括号字符串路径</link>
                    <description>
                            <![CDATA[<p>力扣周赛292--第四题</p><p><a href="https://leetcode.cn/problems/check-if-there-is-a-valid-parentheses-string-path/">2267. 检查是否有合法括号字符串路径</a></p><h1 id="题目">题目</h1><p>一个括号字符串是一个 <strong>非空</strong> 且只包含 <code>'('</code> 和 <code>')'</code> 的字符串。如果下面 <strong>任意</strong> 条件为 <strong>真</strong> ，那么这个括号字符串就是 <strong>合法的</strong> 。</p><ul>     <li>字符串是 <code>()</code> 。</li>     <li>字符串可以表示为 <code>AB</code>（<code>A</code> 连接 <code>B</code>），<code>A</code> 和 <code>B</code> 都是合法括号序列。</li>     <li>字符串可以表示为 <code>(A)</code> ，其中 <code>A</code> 是合法括号序列。</li>  </ul><p>给你一个 <code>m x n</code> 的括号网格图矩阵 <code>grid</code> 。网格图中一个 <strong>合法括号路径</strong> 是满足以下所有条件的一条路径：</p><ul>     <li>路径开始于左上角格子 <code>(0, 0)</code> 。</li>     <li>路径结束于右下角格子 <code>(m - 1, n - 1)</code> 。</li>     <li>路径每次只会向 <strong>下</strong> 或者向 <strong>右</strong> 移动。</li>     <li>路径经过的格子组成的括号字符串是<strong> 合法</strong> 的。</li>  </ul><p>如果网格图中存在一条 <strong>合法括号路径</strong> ，请返回 <code>true</code> ，否则返回 <code>false</code> 。</p><p> </p><p><strong>示例 1：</strong></p><p><img alt="" src="https://assets.leetcode.com/uploads/2022/03/15/example1drawio.png" style="width: 521px; height: 300px;" /></p><pre>  <b>输入：</b>grid = [["(","(","("],[")","(",")"],["(","(",")"],["(","(",")"]]  <b>输出：</b>true  <b>解释：</b>上图展示了两条路径，它们都是合法括号字符串路径。  第一条路径得到的合法字符串是 "()(())" 。  第二条路径得到的合法字符串是 "((()))" 。  注意可能有其他的合法括号字符串路径。  </pre><p><strong>示例 2：</strong></p><p><img alt="" src="https://assets.leetcode.com/uploads/2022/03/15/example2drawio.png" style="width: 165px; height: 165px;" /></p><pre>  <b>输入：</b>grid = [[")",")"],["(","("]]  <b>输出：</b>false  <b>解释：</b>两条可行路径分别得到 "))(" 和 ")((" 。由于它们都不是合法括号字符串，我们返回 false 。  </pre><p> </p><p><strong>提示：</strong></p><ul>     <li><code>m == grid.length</code></li>     <li><code>n == grid[i].length</code></li>     <li><code>1 <= m, n <= 100</code></li>     <li><code>grid[i][j]</code> 要么是 <code>'('</code> ，要么是 <code>')'</code> 。</li>  </ul><h1 id="思路">思路</h1><p>从左上角到右下角，依次路过，下一个坐标一定是该坐标的右侧或者下侧的坐标，同时玩吗记录下路过到该坐标时未配对的’<code>'('</code>的个数，如果是负数则这条路不对旧不用继续下去了。然后一直到右下角的时候，如果个数为1，则满足条件</p><h1 id="代码">代码</h1><p>Java</p><pre><code class="language-java">class Solution {    public boolean hasValidPath(char[][] grid) {        xl = grid.length;        yl = grid[0].length;        use = new boolean[xl][yl][xl * yl];        if ((xl + yl) % 2 == 0 || grid[0][0] == ')' || grid[xl - 1][yl -             return false;        }        dfs(grid, 0, 0, 0);        return bl;    }    int xl;    int yl;    boolean bl = false;    boolean[][][] use;    private void dfs(char[][] grid, int x, int y, int cnt) {        if (x &gt;= xl || y &gt;= yl || cnt &gt; xl - x + yl - y - 1) {            return;        }        if (x == xl - 1 &amp;&amp; y == yl - 1) {            bl = cnt == 1;        }        if (use[x][y][cnt]) {            return;        }        use[x][y][cnt] = true;        cnt += grid[x][y] == '(' ? 1 : -1;        if (cnt &lt; 0) {            return;        }        if (!bl) {            dfs(grid, x + 1, y, cnt);        }        if (!bl) {            dfs(grid, x, y + 1, cnt);        }    }}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 10 May 2022 10:50:21 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2266. 统计打字方案数]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2266统计打字方案数</link>
                    <description>
                            <![CDATA[<p>力扣周赛292--第三题</p><p><a href="https://leetcode.cn/problems/count-number-of-texts/">2266. 统计打字方案数</a></p><h1 id="题目">题目</h1><p>Alice 在给 Bob 用手机打字。数字到字母的 <strong>对应</strong> 如下图所示。</p><p><img alt="" src="https://assets.leetcode.com/uploads/2022/03/15/1200px-telephone-keypad2svg.png" style="width: 200px; height: 162px;"></p><p>为了 <strong>打出</strong> 一个字母，Alice 需要 <strong>按</strong> 对应字母 <code>i</code> 次，<code>i</code> 是该字母在这个按键上所处的位置。</p><ul>     <li>比方说，为了按出字母 <code>'s'</code> ，Alice 需要按 <code>'7'</code> 四次。类似的， Alice 需要按 <code>'5'</code> 两次得到字母  <code>'k'</code> 。</li>     <li>注意，数字 <code>'0'</code> 和 <code>'1'</code> 不映射到任何字母，所以 Alice <strong>不</strong> 使用它们。</li>  </ul><p>但是，由于传输的错误，Bob 没有收到 Alice 打字的字母信息，反而收到了 <strong>按键的字符串信息</strong> 。</p><ul>     <li>比方说，Alice 发出的信息为 <code>"bob"</code> ，Bob 将收到字符串 <code>"2266622"</code> 。</li>  </ul><p>给你一个字符串 <code>pressedKeys</code> ，表示 Bob 收到的字符串，请你返回 Alice <strong>总共可能发出多少种文字信息</strong> 。</p><p>由于答案可能很大，将它对 <code>10<sup>9</sup> + 7</code> <strong>取余</strong> 后返回。</p><p> </p><p><strong>示例 1：</strong></p><pre><b>输入：</b>pressedKeys = "22233"  <b>输出：</b>8  <strong>解释：</strong>  Alice 可能发出的文字信息包括：  "aaadd", "abdd", "badd", "cdd", "aaae", "abe", "bae" 和 "ce" 。  由于总共有 8 种可能的信息，所以我们返回 8 。  </pre><p><strong>示例 2：</strong></p><pre><b>输入：</b>pressedKeys = "222222222222222222222222222222222222"  <b>输出：</b>82876089  <strong>解释：</strong>  总共有 2082876103 种 Alice 可能发出的文字信息。  由于我们需要将答案对 10<sup>9</sup> + 7 取余，所以我们返回 2082876103 % (10<sup>9</sup> + 7) = 82876089 。  </pre><p> </p><p><strong>提示：</strong></p><ul>     <li><code>1 <= pressedKeys.length <= 10<sup>5</sup></code></li>     <li><code>pressedKeys</code> 只包含数字 <code>'2'</code> 到 <code>'9'</code> 。</li>  </ul><h1 id="思路">思路</h1><p>这题标的是中等题，个人觉得解题方法有点取巧，怎么取巧尼，因为重复的数最多4个，我完全可以嵌套3层if来处理，当然我也是这么干的。只要遍历一遍就可以了。</p><p>在遍历到索引<code>i</code>时，有如下情况：</p><ol><li><p>当前数字不和前面的组合，自己单独成一个新的</p><p>索引<code>i</code>的种数 = 索引<code>i-1</code>的种数</p></li><li><p>当前数字与前一个相等，那么该数字的组合就有两种情况</p><ul><li><p>直接和前一个数字凑一起</p><p>索引<code>i</code>的种数=索引<code>i-2</code>的种数</p></li><li><p>索引<code>i</code>的数字和索引<code>i-2</code>的数字也相等，这也有2种情况</p><ul><li><p>把索引<code>i</code>,<code>i-1</code>,<code>i-2</code>都凑一起</p><p>索引<code>i</code>的种数=索引<code>i-3</code>的种数</p></li><li><p>索引<code>i</code>是7或者9，最多可以连续4个，把索引<code>i</code>,<code>i-1</code>,<code>i-2</code>,<code>i-3</code>都凑一起</p><p>索引<code>i</code>的种数=索引<code>i-4</code>的种数</p></li></ul></li></ul></li></ol><p>同时，为了保证数据没有超过int的最大值，这里对于每一次的结果都对<span>10<sup>9</sup>+7</span>取余</p><h1 id="代码">代码</h1><p>Java：</p><pre><code class="language-java">class Solution {    public int countTexts(String pressedKeys) {        int[] cnts = new int[pressedKeys.length() + 1];        cnts[0] = 1;        cnts[1] = 1;        int mod = 1000000007;        for (int i = 1; i &lt; pressedKeys.length(); i++) {            cnts[i + 1] = cnts[i];            if (pressdKeys.charAt(i) == pressedKeys.charAt(i - 1)) {                cnts[i + 1] += cnts[i - 1];                cnts[i + 1] %= mod;                if (i &gt; 1 &amp;&amp; pressedKeys.charAt(i) == pressedKeys.charAt(i - 2)) {                    cnts[i + 1] += cnts[i - 2];                    cnts[i + 1] %= mod;                    if (i &gt; 2 &amp;&amp; pressedKeys.charAt(i) == pressedKeys.charAt(i - 3) &amp;&amp; (pressedKeys.charAt(i) == '7' || pressedKeys.charAt(i) == '                        cnts[i + 1] += cnts[i - 3];                        cnts[i + 1] %= mod;                    }                }            }        }        return cnts[pressedKeys.length()];    }}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 10 May 2022 09:32:53 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣6057. 统计值等于子树平均值的节点数]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣6057统计值等于子树平均值的节点数</link>
                    <description>
                            <![CDATA[<p>力扣周赛292--第二题</p><p><a href="https://leetcode.cn/problems/count-nodes-equal-to-average-of-subtree/">6057. 统计值等于子树平均值的节点数</a></p><h1 id="题目">题目</h1><p>给你一棵二叉树的根节点 <code>root</code> ，找出并返回满足要求的节点数，要求节点的值等于其 <strong>子树</strong> 中值的 <strong>平均值</strong> 。</p><p><strong>注意：</strong></p><ul>     <li><code>n</code> 个元素的平均值可以由 <code>n</code> 个元素 <strong>求和</strong> 然后再除以 <code>n</code> ，并 <strong>向下舍入</strong> 到最近的整数。</li>     <li><code>root</code> 的 <strong>子树</strong> 由 <code>root</code> 和它的所有后代组成。</li>  </ul><p> </p><p><strong>示例 1：</strong></p>  <img src="20220426172421.png" style="width: 300px; height: 212px;">  <pre><strong>输入：</strong>root = [4,8,5,0,1,null,6]  <strong>输出：</strong>5  <strong>解释：</strong>  对值为 4 的节点：子树的平均值 (4 + 8 + 5 + 0 + 1 + 6) / 6 = 24 / 6 = 4 。  对值为 5 的节点：子树的平均值 (5 + 6) / 2 = 11 / 2 = 5 。  对值为 0 的节点：子树的平均值 0 / 1 = 0 。  对值为 1 的节点：子树的平均值 1 / 1 = 1 。  对值为 6 的节点：子树的平均值 6 / 1 = 6 。  </pre><p><strong>示例 2：</strong></p>  <img src="image-20220326133920.png" style="width: 80px; height: 76px;">  <pre><strong>输入：</strong>root = [1]  <strong>输出：</strong>1  <strong>解释：</strong>对值为 1 的节点：子树的平均值 1 / 1 = 1。  </pre><p> </p><p><strong>提示：</strong></p><ul>     <li>树中节点数目在范围 <code>[1, 1000]</code> 内</li>     <li><code>0 <= Node.val <= 1000</code></li>  </ul><h1 id="思路">思路</h1><p>这题的思路：</p><ul><li><p>先深度遍历树，统计出每个节点包含的节点数，并将其放入队列中</p></li><li><p>再深度遍历一次树，这次计算出每个节点的元素和，并从队列中取到该节点的节点数，然后求平均值做判断</p></li></ul><h1 id="代码">代码</h1><p>Java：</p><pre><code class="language-java">class Solution {    public int averageOfSubtree(TreeNode root) {        counts(root);        sums(root);        return count;    }    Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();    int count = 0;    private int counts(TreeNode root) {        if (root == null) {            return 0;        }        int cnt = counts(root.left) + counts(root.right) + 1;        queue.add(cnt);        return cnt;    }    private int sums(TreeNode root) {        if (root == null) {            return 0;        }        int sum = root.val;        sum += sums(root.left);        sum += sums(root.right);        if (sum / queue.poll() == root.val) {            count++;        }        return sum;    }}</code></pre>]]>
                    </description>
                    <pubDate>Mon, 09 May 2022 15:57:48 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2264. 字符串中最大的 3 位相同数字]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2264字符串中最大的3位相同数字</link>
                    <description>
                            <![CDATA[<p>力扣周赛292--第一题</p><p><a href="https://leetcode.cn/problems/largest-3-same-digit-number-in-string/">2264. 字符串中最大的 3 位相同数字</a></p><h1 id="题目">题目</h1><p>给你一个字符串 <code>num</code> ，表示一个大整数。如果一个整数满足下述所有条件，则认为该整数是一个 <strong>优质整数</strong> ：</p><ul>     <li>该整数是 <code>num</code> 的一个长度为 <code>3</code> 的 <strong>子字符串</strong> 。</li>     <li>该整数由唯一一个数字重复 <code>3</code> 次组成。</li>  </ul><p>以字符串形式返回 <strong>最大的优质整数</strong> 。如果不存在满足要求的整数，则返回一个空字符串 <code>""</code> 。</p><p><strong>注意：</strong></p><ul>     <li><strong>子字符串</strong> 是字符串中的一个连续字符序列。</li>     <li><code>num</code> 或优质整数中可能存在 <strong>前导零</strong> 。</li>  </ul><p><strong>示例 1：</strong></p><pre>  <strong>输入：</strong>num = "6<em><strong>777</strong></em>133339"  <strong>输出：</strong>"777"  <strong>解释：</strong>num 中存在两个优质整数："777" 和 "333" 。  "777" 是最大的那个，所以返回 "777" 。  </pre><p><strong>示例 2：</strong></p><pre>  <strong>输入：</strong>num = "23<em><strong>000</strong></em>19"  <strong>输出：</strong>"000"  <strong>解释：</strong>"000" 是唯一一个优质整数。  </pre><p><strong>示例 3：</strong></p><pre>  <strong>输入：</strong>num = "42352338"  <strong>输出：</strong>""  <strong>解释：</strong>不存在长度为 3 且仅由一个唯一数字组成的整数。因此，不存在优质整数。  </pre><p> </p><p><strong>提示：</strong></p><ul>     <li><code>3 <= num.length <= 1000</code></li>     <li><code>num</code> 仅由数字（<code>0</code> - <code>9</code>）组成</li>  </ul><h1 id="思路">思路</h1><p>这题是要找最大的3个相同数并且3个数是相连的，因为数字的话只有0~9这10个数字，找最大的，那我就从999开始，然后依次888、777。。。000，只要字符串中存在，那就是它了。</p><h1 id="代码">代码</h1><p>java：</p><pre><code class="language-java">public String largestGoodInteger(String num) {    String str;    for (int i = 9; i &gt;= 0; i--) {        str = &quot;&quot; + i + i + i;        if (num.contains(str)) {            return str;        }    }    return &quot;&quot;;}</code></pre>]]>
                    </description>
                    <pubDate>Mon, 09 May 2022 15:24:16 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[RKE方式安装k8s集群和Dashboard]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/rke方式安装k8s集群和dashboard</link>
                    <description>
                            <![CDATA[<h1 id="前言">前言</h1><p>需要在电脑上安装好VirtualBox和Vagrant</p><h1 id="构建3台虚拟机">构建3台虚拟机</h1><h2 id="1编写vagrantfile文件">1、编写Vagrantfile文件</h2><p>内容如下：</p><pre><code>Vagrant.configure(&quot;2&quot;) do |config|  config.vm.box_check_update = false  config.vm.provider 'virtualbox' do |vb|  vb.customize [ &quot;guestproperty&quot;, &quot;set&quot;, :id, &quot;/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold&quot;, 1000 ]  end    $num_instances = 3  # curl https://discovery.etcd.io/new?size=3  (1..$num_instances).each do |i|    config.vm.define &quot;node#{i}&quot; do |node|      node.vm.box = &quot;centos/7&quot;      node.vm.hostname = &quot;node#{i}&quot;      ip = &quot;172.17.8.#{i+100}&quot;      node.vm.network &quot;private_network&quot;, ip: ip      node.vm.provider &quot;virtualbox&quot; do |vb|        vb.memory = &quot;8192&quot;        if i==1 then            vb.cpus = 2        else            vb.cpus = 1        end        vb.name = &quot;node#{i}&quot;      end    end  endend</code></pre><h2 id="2启动3台虚拟机">2、启动3台虚拟机</h2><p>在Vagrantfile文件所在目录的控制台下执行命令：</p><pre><code class="language-bash">vagrant up</code></pre><p>等待完成，完成后，在VirtualBox主页：</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-16-24-28-image.png" alt="" /></p><h1 id="虚拟机配置用户名密码ssh连接">虚拟机配置用户名密码ssh连接</h1><p>3台虚拟机都需要安装</p><p>配置参考：<a href="https://site.huangge1199.cn/189.html">windows下VirtualBox和vagrant组合安装centos</a> 中的“用户名密码ssh”</p><h1 id="虚拟机docker安装">虚拟机docker安装</h1><p>3台虚拟机都需要安装</p><p>安装教程：<a href="[docker安装-龙儿之家](http://192.168.0.198:5080/post/dockerInstall/)">docker安装教程</a></p><h1 id="安装-kubernetes-命令行工具-kubectl">安装 Kubernetes 命令行工具 kubectl</h1><p>3台虚拟机都需要安装</p><p>执行命令：</p><pre><code class="language-shell">yum install wgetwget https://dl.k8s.io/release/v1.24.0/bin/linux/amd64/kubectl &amp;&amp; chmod +x kubectl &amp;&amp; cp kubectl /usr/bin/</code></pre><p>如果报错：curl: (1) Protocol “https not supported or disabled in libcurl</p><h1 id="安装rke命令行工具">安装RKE命令行工具</h1><p>只有主节点做即可</p><pre><code class="language-shell">wget https://rancher-mirror.rancher.cn/rke/v1.3.10/rke_linux-amd64 &amp;&amp; mv rke_linux-amd64 rke &amp;&amp; chmod +x rke &amp;&amp; ./rke --version &amp;&amp; cp rke /usr/bin/</code></pre><h1 id="进行机器配置">进行机器配置</h1><p>adduser rke -G docker</p><h2 id="1禁用-selinux">1、禁用 SELinux</h2><pre><code class="language-shell">vi /etc/selinux/config</code></pre><p>将第七行SELINUX=enforcing改为SELINUX=disabled</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-17-23-24-image.png" alt="" /></p><h2 id="2禁用-swap">2、禁用 swap</h2><pre><code class="language-shell">vi /etc/fstab</code></pre><p>使用 # 注释掉有 swap 的一行</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-17-27-23-image.png" alt="" /></p><h2 id="3关闭防火墙">3、关闭防火墙</h2><pre><code class="language-shell">systemctl stop firewalld.servicesystemctl disable firewalld.service</code></pre><h2 id="4重启查看效果">4、重启查看效果</h2><pre><code class="language-shell">reboot/usr/sbin/sestatus -vfree -h</code></pre><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-17-33-11-image.png" alt="" /></p><h2 id="5设置用户">5、设置用户</h2><p>CentOS7不能使用root用户安装</p><p>添加用户：</p><pre><code class="language-shell">adduser rke -G docker</code></pre><p>给新添加的用户设置密码：</p><pre><code class="language-shell">passwd rke</code></pre><p>中途需要输入2次密码</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-17-38-29-image.png" alt="" /></p><p>确认新用户是否有权限：</p><pre><code class="language-shell">su rkedocker ps -a</code></pre><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-17-40-07-image.png" alt="" /></p><h2 id="6设置ssh">6、设置SSH</h2><p>这个地方要给全部的机器配置ssh（包括自己）注意在<mark>新用户</mark>下操作：</p><pre><code class="language-shell">ssh-keygenssh-copy-id rke@172.17.8.101ssh-copy-id rke@172.17.8.102ssh-copy-id rke@172.17.8.103</code></pre><p>第一个红框位置输入yes，第二个红框位置输入密码</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-17-46-31-image.png" alt="" /></p><h1 id="编辑rkeyaml">编辑rke.yaml</h1><p>仅在主节点，在<mark>新用户</mark>下操作</p><pre><code class="language-shell">vi rke.yaml</code></pre><p>rke.yaml内容（里面的IP换成各自的IP哦）：</p><pre><code class="language-yaml">nodes:  - address: 172.17.8.101    user: rke    role: [controlplane, worker, etcd]  - address: 172.17.8.102    user: rke    role: [controlplane, worker, etcd]  - address: 172.17.8.103    user: rke    role: [worker]services:  etcd:    snapshot: true    creation: 6h    retention: 24h# 当使用外部 TLS 终止，并且使用 ingress-nginx v0.22或以上版本时，必须。ingress:  provider: nginx  options:    use-forwarded-headers: “true”ded-headers: “true”</code></pre><h1 id="安装集群">安装集群</h1><p>也是在<mark>新用户</mark>下操作：</p><pre><code class="language-shell">rke up --config rke.yaml</code></pre><p>这步执行时间较长，多等一会，需要下载很多镜像~~~~</p><p>运行完成后执行 ：</p><pre><code class="language-shell">mkdir ~/.kube &amp;&amp; mv kube_config_rke.yaml ~/.kube/config</code></pre><p>最后，执行下面的命令确认集群安装完成</p><pre><code class="language-shell">kubectl get node</code></pre><h1 id="安装kubernetes-dashboard">安装kubernetes Dashboard</h1><p>依然是在新用户下：</p><p>切换到~目录下</p><h2 id="1获取dashboard的yaml文件">1、获取dashboard的yaml文件</h2><pre><code class="language-shell">wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.4/aio/deploy/recommended.yaml</code></pre><h2 id="2修改文件">2、修改文件</h2><p>修改service部分，默认service是ClusterIP类型，这里改称NodePort类型，是集群外部能否访问</p><p>下面标红框的地方为新增加的：</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-19-41-19-image.png" alt="" /></p><h2 id="3执行yaml文件">3、执行yaml文件</h2><pre><code class="language-shell">kubectl apply -f recommended.yaml</code></pre><h2 id="4查看服务状态">4、查看服务状态</h2><pre><code class="language-shell">kubectl get all -n kubernetes-dashboard</code></pre><p>下面红框的可以看出服务已经运行了</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-19-47-43-image.png" alt="" /></p><h2 id="5接下来浏览器访问">5、接下来浏览器访问</h2><p>IP：30010，端口就是你在第二步中添加的，输入网址后，点击高级继续访问就出现下面的页面了</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-19-48-18-image.png" alt="" /></p><h2 id="6创建登录用户信息">6、创建登录用户信息</h2><p>创建文件admin-role.yaml，内容如下：</p><pre><code class="language-yaml">kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:  name: admin  annotations:    rbac.authorization.kubernetes.io/autoupdate: &quot;true&quot;roleRef:  kind: ClusterRole  name: cluster-admin  apiGroup: rbac.authorization.k8s.iosubjects:- kind: ServiceAccount  name: admin  namespace: kube-system---apiVersion: v1kind: ServiceAccountmetadata:  name: admin  namespace: kube-system  labels:    kubernetes.io/cluster-service: &quot;true&quot;    addonmanager.kubernetes.io/mode: Reconcile</code></pre><p>将其执行到集群中：</p><pre><code class="language-shell">kubectl apply -f admin-role.yaml</code></pre><h2 id="7获取token">7、获取token</h2><p>查看kubernetes-dashboard下面的secret</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-20-50-13-image.png" alt="" /></p><p>在执行下面的命令：</p><pre><code class="language-shell">kubectl -n kube-system describe secret 红框的名字</code></pre><p>红框内就是token</p><p><img src="https://img.huangge1199.cn/blog/inRKE/2022-05-04-20-50-57-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Tue, 03 May 2022 16:44:17 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[PhpStorm自动上传修改的内容到服务器]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/phpstorm自动上传修改的内容到服务器</link>
                    <description>
                            <![CDATA[<h1 id="前言">前言</h1><p>今天，在修改WordPress时，发现利用宝塔的在线编辑好麻烦，找到方法，确无法直接跳过去，于是乎，我把代码下载到本地了，本来想着利用编辑器来修改就可以跳转了，没想到呀，PhpStorm给了我一个大惊喜，原来它只要配置好久可以直接在本地修改，WordPress刷新就可以直接看到效果。</p><p>接下来，我就详细的说明一下配置的步骤</p><h1 id="配置步骤">配置步骤</h1><h2 id="1设置连接">1、设置连接</h2><p>打开File--&gt;Setting</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-16-52-00-image.png" alt="" /></p><p>左侧Build,Execution,Deployment--&gt;Deployment，然后右侧加号添加配置选择SFTP</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-16-54-56-image.png" alt="" /></p><p>弹出的窗口内输入配置的名称，可随意输入，方便记住就好</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-20-14-03-image.png" alt="" /></p><p>点击红框的位置添加ssh连接</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-20-12-36-image.png" alt="" /></p><p>在弹出的窗口点击 加号，右边配置</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-20-20-22-image.png" alt="" /></p><p>点击OK后，ssh会自动添加上，同时再把IP加入到下面的红框内</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-20-32-37-image.png" alt="" /></p><h2 id="2设置文件映射关系">2、设置文件映射关系</h2><p>点击mapping，将服务器上项目的根目录添加到Deployment Path中，如果点击OK</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-20-35-23-image.png" alt="" /></p><h2 id="3设置自动上传">3、设置自动上传</h2><p>在PhpStorm中依次点击Tool--&gt;Deployment--&gt;Options...</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-20-42-16-image.png" alt="" /></p><p>在弹出的窗口中，将红框下拉框设置成第二个，之后只要按Ctrl+S就可将修改的代码上传到服务器上</p><p><img src="https://img.huangge1199.cn/blog/phpDeploy/2022-04-30-20-44-58-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sat, 30 Apr 2022 16:40:05 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[设计模式总结与对比（作业）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/设计模式总结与对比作业</link>
                    <description>
                            <![CDATA[<h1 id="1设计模式的初衷是什么有哪些设计原则">1、设计模式的初衷是什么？有哪些设计原则？</h1><ul><li>开闭原则</li><li>依赖倒置原则</li><li>单一职责原则</li><li>接口隔离原则</li><li>迪米特原则</li><li>里氏替换原则</li><li>合成复用原则</li></ul><h1 id="2列举至少4种单例模式被破坏的场景并给出解决方案">2、列举至少4种单例模式被破坏的场景并给出解决方案</h1><ul><li><p>多线程</p><p>解决办法：</p><ul><li><p>改写DCL双重锁的写法</p></li><li><p>使用静态内部类的写法</p></li></ul></li><li><p>指令重排</p><p>解决办法：加volite关键字</p></li><li><p>克隆</p><p>解决办法：在单例对象中重写clone()方法</p></li><li><p>反序列化</p><p>解决方案：反序列化的时候重新readResolve()方法，将返回值设置为单例对象</p></li><li><p>反射</p><p>解决方法：</p><ul><li>在构造方法中检查单例对象，如果已构建则抛出异常</li><li>将单例的实现方式改为枚举式单例</li></ul></li></ul><h1 id="3一句话总结单例模式原型模式建造者模式代理模式策略模式和责任链模式">3、一句话总结单例模式、原型模式、建造者模式、代理模式、策略模式和责任链模式</h1><ul><li><p>单例模式：世界上只有一个Tom</p></li><li><p>原型模式：拔一根猴毛，吹出千万个</p></li><li><p>建造者模式：高配中配与低配，相选哪配就哪配</p></li><li><p>代理模式：没有资源没有时间，得找媒婆来帮忙</p></li><li><p>策略模式：条条大路通北京，具体哪条你来定</p></li><li><p>责任链模式：各人自扫门前雪，莫管他人瓦上霜</p></li></ul>]]>
                    </description>
                    <pubDate>Thu, 28 Apr 2022 10:13:09 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[建造者模式]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/建造者模式</link>
                    <description>
                            <![CDATA[<h1 id="定义">定义</h1><p>建造者模式是将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示</p><p>特征：用户只需指定需要建造的类型就可以获得对象，建造过程及细节不需要了解</p><p>属于创建型模式</p><h1 id="设计中四个角色">设计中四个角色</h1><ul><li>产品（Product）：要创建的产品类对象</li><li>建造者抽象（Builder）：建造者的抽象类，规范产品对象的各个组成部分的构建，一般由子类实现具体的建造过程</li><li>建造者（ConcreBuilder）：具体的Builder类，根据不同的业务逻辑，具体化对象的各个组成部分的创建</li><li>调用者（Director）：调用具体的建造者，来创建对象的各个部分，在指导者中不涉及具体产品的信息，只负责保证对象各部分完整创建或按某种顺序创建</li></ul><h1 id="适用场景">适用场景</h1><ul><li>相同的方法，不同的执行顺序，产生不同的结果时</li><li>多个部件或零件，都可以装配到一个对象中，但是产生的结果又不同时</li><li>产品类非常复杂，或者产品类中的调用顺序不同产生不同的作用</li><li>当初始化一个对象特别复杂，参数多，而且很多参数都具有默认值时</li></ul><h1 id="优点">优点</h1><ul><li>封装性好，创建和使用分离</li><li>拓展性好，建造类之间独立、一定程度上解耦</li></ul><h1 id="缺点">缺点</h1><ul><li>产生多余的Builder对象</li><li>产品内部发生变化，建造者都要修改，成本较大</li></ul><h1 id="建造者模式和工厂模式的区别">建造者模式和工厂模式的区别</h1><ol><li>建造者模式更加注重方法的调用顺序，工厂模式注重于创建对象。</li><li>创建对象的力度不同，建造者模式创建复杂的对象，由各种复杂的部件组成，工厂模式创建出来的都一样。</li><li>关注点：工厂模式模式只需要把对象创建出来就可以了，而建造者模式中不仅要创建出这个对象，还要知道这个对象由哪些部件组成。</li><li>建造者模式根据建造过程中的顺序不一样，最终的对象部件组成也不一样。</li></ol>]]>
                    </description>
                    <pubDate>Tue, 26 Apr 2022 17:10:11 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[原型模式]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/原型模式</link>
                    <description>
                            <![CDATA[<h1 id="定义">定义</h1><p>原型模式时指原型实例指定创建对象的种类，并且通过拷贝这些原型创建新的对象，属于创建型模式</p><h1 id="应用场景">应用场景</h1><ul><li>类初始化消耗资源较多</li><li>new产生的一个对象需要非常繁琐的过程（数据准备、访问权限等）</li><li>构造函数比较复杂</li><li>循环体中生成大量对象时</li></ul><h1 id="优点">优点</h1><ul><li>性能优良，Java自带的原型模式是基于内存二进制流的拷贝，比直接new一个对象性能上提升了许多</li><li>可以使用深克隆方式保存对象的状态，使用原型模式将对象复制一份并将其状态保存起来，简化了创建过程</li></ul><h1 id="缺点">缺点</h1><ul><li>必须配备克隆（或者可拷贝）方法</li><li>当对已有类进行改造的时候，需要修改代码，违反了开闭原则。</li><li>深拷贝、浅拷贝需要运用得当</li></ul><h1 id="克隆破坏单例模式">克隆破坏单例模式</h1><p>如果我们克隆的目标对象是单例的对象，深克隆就会破坏单例。<br />解决办法：可以禁止深克隆。要么你的单例类不实现Cloneable接口；要么我们重写<br />clone()方法，在clone方法中返回单例对象即可</p>]]>
                    </description>
                    <pubDate>Tue, 26 Apr 2022 15:12:44 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[单例模式]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/单例模式</link>
                    <description>
                            <![CDATA[<h1 id="定义">定义</h1><p>确保一个类在任何情况下都绝对只有一个实例，并提供一个全局访问点</p><h1 id="饿汉式单例">饿汉式单例</h1><p>优点：执行效率高、性能高、没有融合的锁</p><p>缺点：某些情况下，可能会造成内存浪费</p><h2 id="常规写法">常规写法</h2><pre><code class="language-java">public class HungrySingleton {    private static final HungrySingleton hungrySingleton = new HungrySingleton();    private HungrySingleton() {    }    public static HungrySingleton getInstance() {        return hungrySingleton;    }}</code></pre><h2 id="利用静态代码块的写法">利用静态代码块的写法</h2><pre><code class="language-java">public class HungryStaticSingleton {    private static final HungryStaticSingleton hungrySingleton;    static {        hungrySingleton = new HungryStaticSingleton();    }    private HungryStaticSingleton() {    }    public static HungryStaticSingleton getInstance() {        return hungrySingleton;    }}</code></pre><h1 id="懒汉式单例">懒汉式单例</h1><h2 id="常规写法-1">常规写法</h2><p>优点：节省了内存，线程安全</p><p>缺点：性能低</p><pre><code class="language-java">public class LazySimpleSingletion {    private static LazySimpleSingletion instance;    private LazySimpleSingletion(){}    public synchronized static LazySimpleSingletion getInstance(){        if(instance == null){            instance = new LazySimpleSingletion();        }        return instance;    }}</code></pre><h2 id="双重检查">双重检查</h2><p>优点：性能高了，线程安全了<br />缺点：可读性难度加大，不够优雅</p><pre><code class="language-java">public class LazyDoubleCheckSingleton {    private volatile static LazyDoubleCheckSingleton instance;    private LazyDoubleCheckSingleton() {    }    public static LazyDoubleCheckSingleton getInstance() {        //检查是否要阻塞        if (instance == null) {            synchronized (LazyDoubleCheckSingleton.class) {                //检查是否要重新创建实例                if (instance == null) {                    instance = new LazyDoubleCheckSingleton();                    //指令重排序的问题                }            }        }        return instance;    }}</code></pre><h2 id="静态内部类单例">静态内部类单例</h2><p>优点：写法优雅，利用了Java本身语法特点，性能高，避免了内存浪费,不能被反射破坏</p><pre><code class="language-java">public class LazyStaticInnerClassSingleton {    private LazyStaticInnerClassSingleton() {        if (LazyHolder.INSTANCE != null) {            throw new RuntimeException(&quot;不允许非法访问&quot;);        }    }    private static LazyStaticInnerClassSingleton getInstance() {        return LazyHolder.INSTANCE;    }    private static class LazyHolder {        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();    }}</code></pre><h1 id="注册式单例">注册式单例</h1><h2 id="枚举单例">枚举单例</h2><pre><code class="language-java">public enum EnumSingleton {    INSTANCE;    private Object data;    public Object getData() {        return data;    }    public void setData(Object data) {        this.data = data;    }    public static EnumSingleton getInstance() {        return INSTANCE;    }}</code></pre><h2 id="容器化单例">容器化单例</h2><pre><code class="language-java">public class ContainerSingleton {    private ContainerSingleton() {    }    private static Map&lt;String, Object&gt; ioc = new ConcurrentHashMap&lt;String, Object&gt;();    public static Object getInstance(String className) {        Object instance = null;        if (!ioc.containsKey(className)) {            try {                instance = Class.forName(className).newInstance();                ioc.put(className, instance);            } catch (Exception e) {                e.printStackTrace();            }            return instance;        } else {            return ioc.get(className);        }    }}</code></pre><h1 id="序列化单例">序列化单例</h1><pre><code class="language-java">public class SeriableSingleton implements Serializable {        public final static SeriableSingleton INSTANCE = new SeriableSingleton();    private SeriableSingleton() {    }    public static SeriableSingleton getInstance() {        return INSTANCE;    }    private Object readResolve() {        return INSTANCE;    }}</code></pre><h1 id="线程">线程</h1><pre><code class="language-java">public class ThreadLocalSingleton {    private static final ThreadLocal&lt;ThreadLocalSingleton&gt; threadLocaLInstance =            new ThreadLocal&lt;ThreadLocalSingleton&gt;() {                @Override                protected ThreadLocalSingleton initialValue() {                    return new ThreadLocalSingleton();                }            };    private ThreadLocalSingleton() {    }    public static ThreadLocalSingleton getInstance() {        return threadLocaLInstance.get();    }}CE;    }}</code></pre><h1 id="破坏单例模式的场景和解决方案">破坏单例模式的场景和解决方案</h1><h2 id="1指令重排使懒汉式模式失效">1、指令重排使懒汉式模式失效</h2><p>解决办法：加<code>volatile</code>关键字</p><h2 id="2反射">2、反射</h2><p>解决办法：弄一个全局变量标记是否实例化过，如果实例化过，抛异常</p><h2 id="3克隆">3、克隆</h2><p>解决办法：重新克隆方法，调用时直接返回已经实例化的对象</p><h2 id="4序列化">4、序列化</h2><p>解决办法：在反序列化时的回调方法 readResolve()中返回单例对象</p>]]>
                    </description>
                    <pubDate>Tue, 26 Apr 2022 14:44:05 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[docker-compose安装Redis]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/docker-compose安装redis</link>
                    <description>
                            <![CDATA[<h1 id="1拉取镜像">1、拉取镜像</h1><p>执行下面的命令拉取redis的docker镜像</p><pre><code class="language-shell">docker pull redis</code></pre><p><img src="https://img.huangge1199.cn/blog/iRedisByDC/2022-04-24-16-57-51-image.png" alt="" /></p><h1 id="2编写docker-composeyml文件">2、编写docker-compose.yml文件</h1><p>内容如下：</p><pre><code class="language-yaml">version: '3'services:  redis:    restart: always    image: redis    container_name: redis    ports:      - 50020:6379    environment:      TZ: Asia/Shanghai    volumes:      - ./data:/data      - ./conf/redis.conf:/etc/redis.conf    privileged: true</code></pre><h1 id="3创建目录文件">3、创建目录文件</h1><p>根据docker-compose.yml文件创建对应目录文件</p><pre><code class="language-shell">pwdmkdir datamkdir confll</code></pre><p><img src="https://img.huangge1199.cn/blog/iRedisByDC/2022-04-24-17-04-13-image.png" alt="" /></p><h1 id="4编写redis的配置文件">4、编写Redis的配置文件</h1><p>在conf目录下创建redis.conf文件，文件内容如下：</p><pre><code># Redis configuration file example.## Note that in order to read the configuration file, Redis must be# started with the file path as first argument:## ./redis-server /path/to/redis.conf# Note on units: when memory size is needed, it is possible to specify# it in the usual form of 1k 5GB 4M and so forth:## 1k =&gt; 1000 bytes# 1kb =&gt; 1024 bytes# 1m =&gt; 1000000 bytes# 1mb =&gt; 1024*1024 bytes# 1g =&gt; 1000000000 bytes# 1gb =&gt; 1024*1024*1024 bytes## units are case insensitive so 1GB 1Gb 1gB are all the same.################################## INCLUDES #################################### Include one or more other config files here.  This is useful if you# have a standard template that goes to all Redis servers but also need# to customize a few per-server settings.  Include files can include# other files, so use this wisely.## Note that option &quot;include&quot; won't be rewritten by command &quot;CONFIG REWRITE&quot;# from admin or Redis Sentinel. Since Redis always uses the last processed# line as value of a configuration directive, you'd better put includes# at the beginning of this file to avoid overwriting config change at runtime.## If instead you are interested in using includes to override configuration# options, it is better to use include as the last line.## include /path/to/local.conf# include /path/to/other.conf################################## MODULES ###################################### Load modules at startup. If the server is not able to load modules# it will abort. It is possible to use multiple loadmodule directives.## loadmodule /path/to/my_module.so# loadmodule /path/to/other_module.so################################## NETWORK ###################################### By default, if no &quot;bind&quot; configuration directive is specified, Redis listens# for connections from all available network interfaces on the host machine.# It is possible to listen to just one or multiple selected interfaces using# the &quot;bind&quot; configuration directive, followed by one or more IP addresses.# Each address can be prefixed by &quot;-&quot;, which means that redis will not fail to# start if the address is not available. Being not available only refers to# addresses that does not correspond to any network interfece. Addresses that# are already in use will always fail, and unsupported protocols will always BE# silently skipped.## Examples:## bind 192.168.1.100 10.0.0.1     # listens on two specific IPv4 addresses# bind 127.0.0.1 ::1              # listens on loopback IPv4 and IPv6# bind * -::*                     # like the default, all available interfaces## ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the# internet, binding to all the interfaces is dangerous and will expose the# instance to everybody on the internet. So by default we uncomment the# following bind directive, that will force Redis to listen only on the# IPv4 and IPv6 (if available) loopback interface addresses (this means Redis# will only be able to accept client connections from the same host that it is# running on).## IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES# JUST COMMENT OUT THE FOLLOWING LINE.# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# bind 127.0.0.1 -::1# Protected mode is a layer of security protection, in order to avoid that# Redis instances left open on the internet are accessed and exploited.## When protected mode is on and if:## 1) The server is not binding explicitly to a set of addresses using the#    &quot;bind&quot; directive.# 2) No password is configured.## The server only accepts connections from clients connecting from the# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain# sockets.## By default protected mode is enabled. You should disable it only if# you are sure you want clients from other hosts to connect to Redis# even if no authentication is configured, nor a specific set of interfaces# are explicitly listed using the &quot;bind&quot; directive.protected-mode no# Accept connections on the specified port, default is 6379 (IANA #815344).# If port 0 is specified Redis will not listen on a TCP socket.port 6379# TCP listen() backlog.## In high requests-per-second environments you need a high backlog in order# to avoid slow clients connection issues. Note that the Linux kernel# will silently truncate it to the value of /proc/sys/net/core/somaxconn so# make sure to raise both the value of somaxconn and tcp_max_syn_backlog# in order to get the desired effect.tcp-backlog 511# Unix socket.## Specify the path for the Unix socket that will be used to listen for# incoming connections. There is no default, so Redis will not listen# on a unix socket when not specified.## unixsocket /run/redis.sock# unixsocketperm 700# Close the connection after a client is idle for N seconds (0 to disable)timeout 0# TCP keepalive.## If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence# of communication. This is useful for two reasons:## 1) Detect dead peers.# 2) Force network equipment in the middle to consider the connection to be#    alive.## On Linux, the specified value (in seconds) is the period used to send ACKs.# Note that to close the connection the double of the time is needed.# On other kernels the period depends on the kernel configuration.## A reasonable value for this option is 300 seconds, which is the new# Redis default starting with Redis 3.2.1.tcp-keepalive 300################################# TLS/SSL ###################################### By default, TLS/SSL is disabled. To enable it, the &quot;tls-port&quot; configuration# directive can be used to define TLS-listening ports. To enable TLS on the# default port, use:## port 0# tls-port 6379# Configure a X.509 certificate and private key to use for authenticating the# server to connected clients, masters or cluster peers.  These files should be# PEM formatted.## tls-cert-file redis.crt # tls-key-file redis.key## If the key file is encrypted using a passphrase, it can be included here# as well.## tls-key-file-pass secret# Normally Redis uses the same certificate for both server functions (accepting# connections) and client functions (replicating from a master, establishing# cluster bus connections, etc.).## Sometimes certificates are issued with attributes that designate them as# client-only or server-only certificates. In that case it may be desired to use# different certificates for incoming (server) and outgoing (client)# connections. To do that, use the following directives:## tls-client-cert-file client.crt# tls-client-key-file client.key## If the key file is encrypted using a passphrase, it can be included here# as well.## tls-client-key-file-pass secret# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:## tls-dh-params-file redis.dh# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL# clients and peers.  Redis requires an explicit configuration of at least one# of these, and will not implicitly use the system wide configuration.## tls-ca-cert-file ca.crt# tls-ca-cert-dir /etc/ssl/certs# By default, clients (including replica servers) on a TLS port are required# to authenticate using valid client side certificates.## If &quot;no&quot; is specified, client certificates are not required and not accepted.# If &quot;optional&quot; is specified, client certificates are accepted and must be# valid if provided, but are not required.## tls-auth-clients no# tls-auth-clients optional# By default, a Redis replica does not attempt to establish a TLS connection# with its master.## Use the following directive to enable TLS on replication links.## tls-replication yes# By default, the Redis Cluster bus uses a plain TCP connection. To enable# TLS for the bus protocol, use the following directive:## tls-cluster yes# By default, only TLSv1.2 and TLSv1.3 are enabled and it is highly recommended# that older formally deprecated versions are kept disabled to reduce the attack surface.# You can explicitly specify TLS versions to support.# Allowed values are case insensitive and include &quot;TLSv1&quot;, &quot;TLSv1.1&quot;, &quot;TLSv1.2&quot;,# &quot;TLSv1.3&quot; (OpenSSL &gt;= 1.1.1) or any combination.# To enable only TLSv1.2 and TLSv1.3, use:## tls-protocols &quot;TLSv1.2 TLSv1.3&quot;# Configure allowed ciphers.  See the ciphers(1ssl) manpage for more information# about the syntax of this string.## Note: this configuration applies only to &lt;= TLSv1.2.## tls-ciphers DEFAULT:!MEDIUM# Configure allowed TLSv1.3 ciphersuites.  See the ciphers(1ssl) manpage for more# information about the syntax of this string, and specifically for TLSv1.3# ciphersuites.## tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256# When choosing a cipher, use the server's preference instead of the client# preference. By default, the server follows the client's preference.## tls-prefer-server-ciphers yes# By default, TLS session caching is enabled to allow faster and less expensive# reconnections by clients that support it. Use the following directive to disable# caching.## tls-session-caching no# Change the default number of TLS sessions cached. A zero value sets the cache# to unlimited size. The default size is 20480.## tls-session-cache-size 5000# Change the default timeout of cached TLS sessions. The default timeout is 300# seconds.## tls-session-cache-timeout 60################################# GENERAL ###################################### By default Redis does not run as a daemon. Use 'yes' if you need it.# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.# When Redis is supervised by upstart or systemd, this parameter has no impact.daemonize no# If you run Redis from upstart or systemd, Redis can interact with your# supervision tree. Options:#   supervised no      - no supervision interaction#   supervised upstart - signal upstart by putting Redis into SIGSTOP mode#                        requires &quot;expect stop&quot; in your upstart job config#   supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET#                        on startup, and updating Redis status on a regular#                        basis.#   supervised auto    - detect upstart or systemd method based on#                        UPSTART_JOB or NOTIFY_SOCKET environment variables# Note: these supervision methods only signal &quot;process is ready.&quot;#       They do not enable continuous pings back to your supervisor.## The default is &quot;no&quot;. To run under upstart/systemd, you can simply uncomment# the line below:## supervised auto# If a pid file is specified, Redis writes it where specified at startup# and removes it at exit.## When the server runs non daemonized, no pid file is created if none is# specified in the configuration. When the server is daemonized, the pid file# is used even if not specified, defaulting to &quot;/var/run/redis.pid&quot;.## Creating a pid file is best effort: if Redis is not able to create it# nothing bad happens, the server will start and run normally.## Note that on modern Linux systems &quot;/run/redis.pid&quot; is more conforming# and should be used instead.pidfile /var/run/redis_6379.pid# Specify the server verbosity level.# This can be one of:# debug (a lot of information, useful for development/testing)# verbose (many rarely useful info, but not a mess like the debug level)# notice (moderately verbose, what you want in production probably)# warning (only very important / critical messages are logged)loglevel notice# Specify the log file name. Also the empty string can be used to force# Redis to log on the standard output. Note that if you use standard# output for logging but daemonize, logs will be sent to /dev/nulllogfile &quot;&quot;# To enable logging to the system logger, just set 'syslog-enabled' to yes,# and optionally update the other syslog parameters to suit your needs.# syslog-enabled no# Specify the syslog identity.# syslog-ident redis# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.# syslog-facility local0# To disable the built in crash log, which will possibly produce cleaner core# dumps when they are needed, uncomment the following:## crash-log-enabled no# To disable the fast memory check that's run as part of the crash log, which# will possibly let redis terminate sooner, uncomment the following:## crash-memcheck-enabled no# Set the number of databases. The default database is DB 0, you can select# a different one on a per-connection basis using SELECT &lt;dbid&gt; where# dbid is a number between 0 and 'databases'-1databases 16# By default Redis shows an ASCII art logo only when started to log to the# standard output and if the standard output is a TTY and syslog logging is# disabled. Basically this means that normally a logo is displayed only in# interactive sessions.## However it is possible to force the pre-4.0 behavior and always show a# ASCII art logo in startup logs by setting the following option to yes.always-show-logo no# By default, Redis modifies the process title (as seen in 'top' and 'ps') to# provide some runtime information. It is possible to disable this and leave# the process name as executed by setting the following to no.set-proc-title yes# When changing the process title, Redis uses the following template to construct# the modified title.## Template variables are specified in curly brackets. The following variables are# supported:## {title}           Name of process as executed if parent, or type of child process.# {listen-addr}     Bind address or '*' followed by TCP or TLS port listening on, or#                   Unix socket if only that's available.# {server-mode}     Special mode, i.e. &quot;[sentinel]&quot; or &quot;[cluster]&quot;.# {port}            TCP port listening on, or 0.# {tls-port}        TLS port listening on, or 0.# {unixsocket}      Unix domain socket listening on, or &quot;&quot;.# {config-file}     Name of configuration file used.#proc-title-template &quot;{title} {listen-addr} {server-mode}&quot;################################ SNAPSHOTTING  ################################# Save the DB to disk.## save &lt;seconds&gt; &lt;changes&gt;## Redis will save the DB if both the given number of seconds and the given# number of write operations against the DB occurred.## Snapshotting can be completely disabled with a single empty string argument# as in following example:## save &quot;&quot;## Unless specified otherwise, by default Redis will save the DB:#   * After 3600 seconds (an hour) if at least 1 key changed#   * After 300 seconds (5 minutes) if at least 100 keys changed#   * After 60 seconds if at least 10000 keys changed## You can set these explicitly by uncommenting the three following lines.## save 3600 1# save 300 100# save 60 10000# By default Redis will stop accepting writes if RDB snapshots are enabled# (at least one save point) and the latest background save failed.# This will make the user aware (in a hard way) that data is not persisting# on disk properly, otherwise chances are that no one will notice and some# disaster will happen.## If the background saving process will start working again Redis will# automatically allow writes again.## However if you have setup your proper monitoring of the Redis server# and persistence, you may want to disable this feature so that Redis will# continue to work as usual even if there are problems with disk,# permissions, and so forth.stop-writes-on-bgsave-error yes# Compress string objects using LZF when dump .rdb databases?# By default compression is enabled as it's almost always a win.# If you want to save some CPU in the saving child set it to 'no' but# the dataset will likely be bigger if you have compressible values or keys.rdbcompression yes# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.# This makes the format more resistant to corruption but there is a performance# hit to pay (around 10%) when saving and loading RDB files, so you can disable it# for maximum performances.## RDB files created with checksum disabled have a checksum of zero that will# tell the loading code to skip the check.rdbchecksum yes# Enables or disables full sanitation checks for ziplist and listpack etc when# loading an RDB or RESTORE payload. This reduces the chances of a assertion or# crash later on while processing commands.# Options:#   no         - Never perform full sanitation#   yes        - Always perform full sanitation#   clients    - Perform full sanitation only for user connections.#                Excludes: RDB files, RESTORE commands received from the master#                connection, and client connections which have the#                skip-sanitize-payload ACL flag.# The default should be 'clients' but since it currently affects cluster# resharding via MIGRATE, it is temporarily set to 'no' by default.## sanitize-dump-payload no# The filename where to dump the DBdbfilename dump.rdb# Remove RDB files used by replication in instances without persistence# enabled. By default this option is disabled, however there are environments# where for regulations or other security concerns, RDB files persisted on# disk by masters in order to feed replicas, or stored on disk by replicas# in order to load them for the initial synchronization, should be deleted# ASAP. Note that this option ONLY WORKS in instances that have both AOF# and RDB persistence disabled, otherwise is completely ignored.## An alternative (and sometimes better) way to obtain the same effect is# to use diskless replication on both master and replicas instances. However# in the case of replicas, diskless is not always an option.rdb-del-sync-files no# The working directory.## The DB will be written inside this directory, with the filename specified# above using the 'dbfilename' configuration directive.## The Append Only File will also be created inside this directory.## Note that you must specify a directory here, not a file name.dir ./################################# REPLICATION ################################## Master-Replica replication. Use replicaof to make a Redis instance a copy of# another Redis server. A few things to understand ASAP about Redis replication.##   +------------------+      +---------------+#   |      Master      | ---&gt; |    Replica    |#   | (receive writes) |      |  (exact copy) |#   +------------------+      +---------------+## 1) Redis replication is asynchronous, but you can configure a master to#    stop accepting writes if it appears to be not connected with at least#    a given number of replicas.# 2) Redis replicas are able to perform a partial resynchronization with the#    master if the replication link is lost for a relatively small amount of#    time. You may want to configure the replication backlog size (see the next#    sections of this file) with a sensible value depending on your needs.# 3) Replication is automatic and does not need user intervention. After a#    network partition replicas automatically try to reconnect to masters#    and resynchronize with them.## replicaof &lt;masterip&gt; &lt;masterport&gt;# If the master is password protected (using the &quot;requirepass&quot; configuration# directive below) it is possible to tell the replica to authenticate before# starting the replication synchronization process, otherwise the master will# refuse the replica request.## masterauth &lt;master-password&gt;## However this is not enough if you are using Redis ACLs (for Redis version# 6 or greater), and the default user is not capable of running the PSYNC# command and/or other commands needed for replication. In this case it's# better to configure a special user to use with replication, and specify the# masteruser configuration as such:## masteruser &lt;username&gt;## When masteruser is specified, the replica will authenticate against its# master using the new AUTH form: AUTH &lt;username&gt; &lt;password&gt;.# When a replica loses its connection with the master, or when the replication# is still in progress, the replica can act in two different ways:## 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will#    still reply to client requests, possibly with out of date data, or the#    data set may just be empty if this is the first synchronization.## 2) If replica-serve-stale-data is set to 'no' the replica will reply with#    an error &quot;SYNC with master in progress&quot; to all commands except:#    INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE,#    UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST,#    HOST and LATENCY.#replica-serve-stale-data yes# You can configure a replica instance to accept writes or not. Writing against# a replica instance may be useful to store some ephemeral data (because data# written on a replica will be easily deleted after resync with the master) but# may also cause problems if clients are writing to it because of a# misconfiguration.## Since Redis 2.6 by default replicas are read-only.## Note: read only replicas are not designed to be exposed to untrusted clients# on the internet. It's just a protection layer against misuse of the instance.# Still a read only replica exports by default all the administrative commands# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve# security of read only replicas using 'rename-command' to shadow all the# administrative / dangerous commands.replica-read-only yes# Replication SYNC strategy: disk or socket.## New replicas and reconnecting replicas that are not able to continue the# replication process just receiving differences, need to do what is called a# &quot;full synchronization&quot;. An RDB file is transmitted from the master to the# replicas.## The transmission can happen in two different ways:## 1) Disk-backed: The Redis master creates a new process that writes the RDB#                 file on disk. Later the file is transferred by the parent#                 process to the replicas incrementally.# 2) Diskless: The Redis master creates a new process that directly writes the#              RDB file to replica sockets, without touching the disk at all.## With disk-backed replication, while the RDB file is generated, more replicas# can be queued and served with the RDB file as soon as the current child# producing the RDB file finishes its work. With diskless replication instead# once the transfer starts, new replicas arriving will be queued and a new# transfer will start when the current one terminates.## When diskless replication is used, the master waits a configurable amount of# time (in seconds) before starting the transfer in the hope that multiple# replicas will arrive and the transfer can be parallelized.## With slow disks and fast (large bandwidth) networks, diskless replication# works better.repl-diskless-sync no# When diskless replication is enabled, it is possible to configure the delay# the server waits in order to spawn the child that transfers the RDB via socket# to the replicas.## This is important since once the transfer starts, it is not possible to serve# new replicas arriving, that will be queued for the next RDB transfer, so the# server waits a delay in order to let more replicas arrive.## The delay is specified in seconds, and by default is 5 seconds. To disable# it entirely just set it to 0 seconds and the transfer will start ASAP.repl-diskless-sync-delay 5# -----------------------------------------------------------------------------# WARNING: RDB diskless load is experimental. Since in this setup the replica# does not immediately store an RDB on disk, it may cause data loss during# failovers. RDB diskless load + Redis modules not handling I/O reads may also# cause Redis to abort in case of I/O errors during the initial synchronization# stage with the master. Use only if you know what you are doing.# -----------------------------------------------------------------------------## Replica can load the RDB it reads from the replication link directly from the# socket, or store the RDB to a file and read that file after it was completely# received from the master.## In many cases the disk is slower than the network, and storing and loading# the RDB file may increase replication time (and even increase the master's# Copy on Write memory and salve buffers).# However, parsing the RDB file directly from the socket may mean that we have# to flush the contents of the current database before the full rdb was# received. For this reason we have the following options:## &quot;disabled&quot;    - Don't use diskless load (store the rdb file to the disk first)# &quot;on-empty-db&quot; - Use diskless load only when it is completely safe.# &quot;swapdb&quot;      - Keep a copy of the current db contents in RAM while parsing#                 the data directly from the socket. note that this requires#                 sufficient memory, if you don't have it, you risk an OOM kill.repl-diskless-load disabled# Replicas send PINGs to server in a predefined interval. It's possible to# change this interval with the repl_ping_replica_period option. The default# value is 10 seconds.## repl-ping-replica-period 10# The following option sets the replication timeout for:## 1) Bulk transfer I/O during SYNC, from the point of view of replica.# 2) Master timeout from the point of view of replicas (data, pings).# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).## It is important to make sure that this value is greater than the value# specified for repl-ping-replica-period otherwise a timeout will be detected# every time there is low traffic between the master and the replica. The default# value is 60 seconds.## repl-timeout 60# Disable TCP_NODELAY on the replica socket after SYNC?## If you select &quot;yes&quot; Redis will use a smaller number of TCP packets and# less bandwidth to send data to replicas. But this can add a delay for# the data to appear on the replica side, up to 40 milliseconds with# Linux kernels using a default configuration.## If you select &quot;no&quot; the delay for data to appear on the replica side will# be reduced but more bandwidth will be used for replication.## By default we optimize for low latency, but in very high traffic conditions# or when the master and replicas are many hops away, turning this to &quot;yes&quot; may# be a good idea.repl-disable-tcp-nodelay no# Set the replication backlog size. The backlog is a buffer that accumulates# replica data when replicas are disconnected for some time, so that when a# replica wants to reconnect again, often a full resync is not needed, but a# partial resync is enough, just passing the portion of data the replica# missed while disconnected.## The bigger the replication backlog, the longer the replica can endure the# disconnect and later be able to perform a partial resynchronization.## The backlog is only allocated if there is at least one replica connected.## repl-backlog-size 1mb# After a master has no connected replicas for some time, the backlog will be# freed. The following option configures the amount of seconds that need to# elapse, starting from the time the last replica disconnected, for the backlog# buffer to be freed.## Note that replicas never free the backlog for timeout, since they may be# promoted to masters later, and should be able to correctly &quot;partially# resynchronize&quot; with other replicas: hence they should always accumulate backlog.## A value of 0 means to never release the backlog.## repl-backlog-ttl 3600# The replica priority is an integer number published by Redis in the INFO# output. It is used by Redis Sentinel in order to select a replica to promote# into a master if the master is no longer working correctly.## A replica with a low priority number is considered better for promotion, so# for instance if there are three replicas with priority 10, 100, 25 Sentinel# will pick the one with priority 10, that is the lowest.## However a special priority of 0 marks the replica as not able to perform the# role of master, so a replica with priority of 0 will never be selected by# Redis Sentinel for promotion.## By default the priority is 100.replica-priority 100# -----------------------------------------------------------------------------# By default, Redis Sentinel includes all replicas in its reports. A replica# can be excluded from Redis Sentinel's announcements. An unannounced replica# will be ignored by the 'sentinel replicas &lt;master&gt;' command and won't be# exposed to Redis Sentinel's clients.## This option does not change the behavior of replica-priority. Even with# replica-announced set to 'no', the replica can be promoted to master. To# prevent this behavior, set replica-priority to 0.## replica-announced yes# It is possible for a master to stop accepting writes if there are less than# N replicas connected, having a lag less or equal than M seconds.## The N replicas need to be in &quot;online&quot; state.## The lag in seconds, that must be &lt;= the specified value, is calculated from# the last ping received from the replica, that is usually sent every second.## This option does not GUARANTEE that N replicas will accept the write, but# will limit the window of exposure for lost writes in case not enough replicas# are available, to the specified number of seconds.## For example to require at least 3 replicas with a lag &lt;= 10 seconds use:## min-replicas-to-write 3# min-replicas-max-lag 10## Setting one or the other to 0 disables the feature.## By default min-replicas-to-write is set to 0 (feature disabled) and# min-replicas-max-lag is set to 10.# A Redis master is able to list the address and port of the attached# replicas in different ways. For example the &quot;INFO replication&quot; section# offers this information, which is used, among other tools, by# Redis Sentinel in order to discover replica instances.# Another place where this info is available is in the output of the# &quot;ROLE&quot; command of a master.## The listed IP address and port normally reported by a replica is# obtained in the following way:##   IP: The address is auto detected by checking the peer address#   of the socket used by the replica to connect with the master.##   Port: The port is communicated by the replica during the replication#   handshake, and is normally the port that the replica is using to#   listen for connections.## However when port forwarding or Network Address Translation (NAT) is# used, the replica may actually be reachable via different IP and port# pairs. The following two options can be used by a replica in order to# report to its master a specific set of IP and port, so that both INFO# and ROLE will report those values.## There is no need to use both the options if you need to override just# the port or the IP address.## replica-announce-ip 5.5.5.5# replica-announce-port 1234############################### KEYS TRACKING ################################## Redis implements server assisted support for client side caching of values.# This is implemented using an invalidation table that remembers, using# a radix key indexed by key name, what clients have which keys. In turn# this is used in order to send invalidation messages to clients. Please# check this page to understand more about the feature:##   https://redis.io/topics/client-side-caching## When tracking is enabled for a client, all the read only queries are assumed# to be cached: this will force Redis to store information in the invalidation# table. When keys are modified, such information is flushed away, and# invalidation messages are sent to the clients. However if the workload is# heavily dominated by reads, Redis could use more and more memory in order# to track the keys fetched by many clients.## For this reason it is possible to configure a maximum fill value for the# invalidation table. By default it is set to 1M of keys, and once this limit# is reached, Redis will start to evict keys in the invalidation table# even if they were not modified, just to reclaim memory: this will in turn# force the clients to invalidate the cached values. Basically the table# maximum size is a trade off between the memory you want to spend server# side to track information about who cached what, and the ability of clients# to retain cached objects in memory.## If you set the value to 0, it means there are no limits, and Redis will# retain as many keys as needed in the invalidation table.# In the &quot;stats&quot; INFO section, you can find information about the number of# keys in the invalidation table at every given moment.## Note: when key tracking is used in broadcasting mode, no memory is used# in the server side so this setting is useless.## tracking-table-max-keys 1000000################################## SECURITY #################################### Warning: since Redis is pretty fast, an outside user can try up to# 1 million passwords per second against a modern box. This means that you# should use very strong passwords, otherwise they will be very easy to break.# Note that because the password is really a shared secret between the client# and the server, and should not be memorized by any human, the password# can be easily a long string from /dev/urandom or whatever, so by using a# long and unguessable password no brute force attack will be possible.# Redis ACL users are defined in the following format:##   user &lt;username&gt; ... acl rules ...## For example:##   user worker +@list +@connection ~jobs:* on &gt;ffa9203c493aa99## The special username &quot;default&quot; is used for new connections. If this user# has the &quot;nopass&quot; rule, then new connections will be immediately authenticated# as the &quot;default&quot; user without the need of any password provided via the# AUTH command. Otherwise if the &quot;default&quot; user is not flagged with &quot;nopass&quot;# the connections will start in not authenticated state, and will require# AUTH (or the HELLO command AUTH option) in order to be authenticated and# start to work.## The ACL rules that describe what a user can do are the following:##  on           Enable the user: it is possible to authenticate as this user.#  off          Disable the user: it's no longer possible to authenticate#               with this user, however the already authenticated connections#               will still work.#  skip-sanitize-payload    RESTORE dump-payload sanitation is skipped.#  sanitize-payload         RESTORE dump-payload is sanitized (default).#  +&lt;command&gt;   Allow the execution of that command#  -&lt;command&gt;   Disallow the execution of that command#  +@&lt;category&gt; Allow the execution of all the commands in such category#               with valid categories are like @admin, @set, @sortedset, ...#               and so forth, see the full list in the server.c file where#               the Redis command table is described and defined.#               The special category @all means all the commands, but currently#               present in the server, and that will be loaded in the future#               via modules.#  +&lt;command&gt;|subcommand    Allow a specific subcommand of an otherwise#                           disabled command. Note that this form is not#                           allowed as negative like -DEBUG|SEGFAULT, but#                           only additive starting with &quot;+&quot;.#  allcommands  Alias for +@all. Note that it implies the ability to execute#               all the future commands loaded via the modules system.#  nocommands   Alias for -@all.#  ~&lt;pattern&gt;   Add a pattern of keys that can be mentioned as part of#               commands. For instance ~* allows all the keys. The pattern#               is a glob-style pattern like the one of KEYS.#               It is possible to specify multiple patterns.#  allkeys      Alias for ~*#  resetkeys    Flush the list of allowed keys patterns.#  &amp;&lt;pattern&gt;   Add a glob-style pattern of Pub/Sub channels that can be#               accessed by the user. It is possible to specify multiple channel#               patterns.#  allchannels  Alias for &amp;*#  resetchannels            Flush the list of allowed channel patterns.#  &gt;&lt;password&gt;  Add this password to the list of valid password for the user.#               For example &gt;mypass will add &quot;mypass&quot; to the list.#               This directive clears the &quot;nopass&quot; flag (see later).#  &lt;&lt;password&gt;  Remove this password from the list of valid passwords.#  nopass       All the set passwords of the user are removed, and the user#               is flagged as requiring no password: it means that every#               password will work against this user. If this directive is#               used for the default user, every new connection will be#               immediately authenticated with the default user without#               any explicit AUTH command required. Note that the &quot;resetpass&quot;#               directive will clear this condition.#  resetpass    Flush the list of allowed passwords. Moreover removes the#               &quot;nopass&quot; status. After &quot;resetpass&quot; the user has no associated#               passwords and there is no way to authenticate without adding#               some password (or setting it as &quot;nopass&quot; later).#  reset        Performs the following actions: resetpass, resetkeys, off,#               -@all. The user returns to the same state it has immediately#               after its creation.## ACL rules can be specified in any order: for instance you can start with# passwords, then flags, or key patterns. However note that the additive# and subtractive rules will CHANGE MEANING depending on the ordering.# For instance see the following example:##   user alice on +@all -DEBUG ~* &gt;somepassword## This will allow &quot;alice&quot; to use all the commands with the exception of the# DEBUG command, since +@all added all the commands to the set of the commands# alice can use, and later DEBUG was removed. However if we invert the order# of two ACL rules the result will be different:##   user alice on -DEBUG +@all ~* &gt;somepassword## Now DEBUG was removed when alice had yet no commands in the set of allowed# commands, later all the commands are added, so the user will be able to# execute everything.## Basically ACL rules are processed left-to-right.## For more information about ACL configuration please refer to# the Redis web site at https://redis.io/topics/acl# ACL LOG## The ACL Log tracks failed commands and authentication events associated# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked # by ACLs. The ACL Log is stored in memory. You can reclaim memory with # ACL LOG RESET. Define the maximum entry length of the ACL Log below.acllog-max-len 128# Using an external ACL file## Instead of configuring users here in this file, it is possible to use# a stand-alone file just listing users. The two methods cannot be mixed:# if you configure users here and at the same time you activate the external# ACL file, the server will refuse to start.## The format of the external ACL user file is exactly the same as the# format that is used inside redis.conf to describe users.## aclfile /etc/redis/users.acl# IMPORTANT NOTE: starting with Redis 6 &quot;requirepass&quot; is just a compatibility# layer on top of the new ACL system. The option effect will be just setting# the password for the default user. Clients will still authenticate using# AUTH &lt;password&gt; as usually, or more explicitly with AUTH default &lt;password&gt;# if they follow the new protocol: both will work.## The requirepass is not compatable with aclfile option and the ACL LOAD# command, these will cause requirepass to be ignored.#requirepass huangge1199# New users are initialized with restrictive permissions by default, via the# equivalent of this ACL rule 'off resetkeys -@all'. Starting with Redis 6.2, it# is possible to manage access to Pub/Sub channels with ACL rules as well. The# default Pub/Sub channels permission if new users is controlled by the # acl-pubsub-default configuration directive, which accepts one of these values:## allchannels: grants access to all Pub/Sub channels# resetchannels: revokes access to all Pub/Sub channels## To ensure backward compatibility while upgrading Redis 6.0, acl-pubsub-default# defaults to the 'allchannels' permission.## Future compatibility note: it is very likely that in a future version of Redis# the directive's default of 'allchannels' will be changed to 'resetchannels' in# order to provide better out-of-the-box Pub/Sub security. Therefore, it is# recommended that you explicitly define Pub/Sub permissions for all users# rather then rely on implicit default values. Once you've set explicit# Pub/Sub for all existing users, you should uncomment the following line.## acl-pubsub-default resetchannels# Command renaming (DEPRECATED).## ------------------------------------------------------------------------# WARNING: avoid using this option if possible. Instead use ACLs to remove# commands from the default user, and put them only in some admin user you# create for administrative purposes.# ------------------------------------------------------------------------## It is possible to change the name of dangerous commands in a shared# environment. For instance the CONFIG command may be renamed into something# hard to guess so that it will still be available for internal-use tools# but not available for general clients.## Example:## rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52## It is also possible to completely kill a command by renaming it into# an empty string:## rename-command CONFIG &quot;&quot;## Please note that changing the name of commands that are logged into the# AOF file or transmitted to replicas may cause problems.################################### CLIENTS ##################################### Set the max number of connected clients at the same time. By default# this limit is set to 10000 clients, however if the Redis server is not# able to configure the process file limit to allow for the specified limit# the max number of allowed clients is set to the current file limit# minus 32 (as Redis reserves a few file descriptors for internal uses).## Once the limit is reached Redis will close all the new connections sending# an error 'max number of clients reached'.## IMPORTANT: When Redis Cluster is used, the max number of connections is also# shared with the cluster bus: every node in the cluster will use two# connections, one incoming and another outgoing. It is important to size the# limit accordingly in case of very large clusters.## maxclients 10000############################## MEMORY MANAGEMENT ################################# Set a memory usage limit to the specified amount of bytes.# When the memory limit is reached Redis will try to remove keys# according to the eviction policy selected (see maxmemory-policy).## If Redis can't remove keys according to the policy, or if the policy is# set to 'noeviction', Redis will start to reply with errors to commands# that would use more memory, like SET, LPUSH, and so on, and will continue# to reply to read-only commands like GET.## This option is usually useful when using Redis as an LRU or LFU cache, or to# set a hard memory limit for an instance (using the 'noeviction' policy).## WARNING: If you have replicas attached to an instance with maxmemory on,# the size of the output buffers needed to feed the replicas are subtracted# from the used memory count, so that network problems / resyncs will# not trigger a loop where keys are evicted, and in turn the output# buffer of replicas is full with DELs of keys evicted triggering the deletion# of more keys, and so forth until the database is completely emptied.## In short... if you have replicas attached it is suggested that you set a lower# limit for maxmemory so that there is some free RAM on the system for replica# output buffers (but this is not needed if the policy is 'noeviction').## maxmemory &lt;bytes&gt;# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory# is reached. You can select one from the following behaviors:## volatile-lru -&gt; Evict using approximated LRU, only keys with an expire set.# allkeys-lru -&gt; Evict any key using approximated LRU.# volatile-lfu -&gt; Evict using approximated LFU, only keys with an expire set.# allkeys-lfu -&gt; Evict any key using approximated LFU.# volatile-random -&gt; Remove a random key having an expire set.# allkeys-random -&gt; Remove a random key, any key.# volatile-ttl -&gt; Remove the key with the nearest expire time (minor TTL)# noeviction -&gt; Don't evict anything, just return an error on write operations.## LRU means Least Recently Used# LFU means Least Frequently Used## Both LRU, LFU and volatile-ttl are implemented using approximated# randomized algorithms.## Note: with any of the above policies, when there are no suitable keys for# eviction, Redis will return an error on write operations that require# more memory. These are usually commands that create new keys, add data or# modify existing keys. A few examples are: SET, INCR, HSET, LPUSH, SUNIONSTORE,# SORT (due to the STORE argument), and EXEC (if the transaction includes any# command that requires memory).## The default is:## maxmemory-policy noeviction# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated# algorithms (in order to save memory), so you can tune it for speed or# accuracy. By default Redis will check five keys and pick the one that was# used least recently, you can change the sample size using the following# configuration directive.## The default of 5 produces good enough results. 10 Approximates very closely# true LRU but costs more CPU. 3 is faster but not very accurate.## maxmemory-samples 5# Eviction processing is designed to function well with the default setting.# If there is an unusually large amount of write traffic, this value may need to# be increased.  Decreasing this value may reduce latency at the risk of # eviction processing effectiveness#   0 = minimum latency, 10 = default, 100 = process without regard to latency## maxmemory-eviction-tenacity 10# Starting from Redis 5, by default a replica will ignore its maxmemory setting# (unless it is promoted to master after a failover or manually). It means# that the eviction of keys will be just handled by the master, sending the# DEL commands to the replica as keys evict in the master side.## This behavior ensures that masters and replicas stay consistent, and is usually# what you want, however if your replica is writable, or you want the replica# to have a different memory setting, and you are sure all the writes performed# to the replica are idempotent, then you may change this default (but be sure# to understand what you are doing).## Note that since the replica by default does not evict, it may end using more# memory than the one set via maxmemory (there are certain buffers that may# be larger on the replica, or data structures may sometimes take more memory# and so forth). So make sure you monitor your replicas and make sure they# have enough memory to never hit a real out-of-memory condition before the# master hits the configured maxmemory setting.## replica-ignore-maxmemory yes# Redis reclaims expired keys in two ways: upon access when those keys are# found to be expired, and also in background, in what is called the# &quot;active expire key&quot;. The key space is slowly and interactively scanned# looking for expired keys to reclaim, so that it is possible to free memory# of keys that are expired and will never be accessed again in a short time.## The default effort of the expire cycle will try to avoid having more than# ten percent of expired keys still in memory, and will try to avoid consuming# more than 25% of total memory and to add latency to the system. However# it is possible to increase the expire &quot;effort&quot; that is normally set to# &quot;1&quot;, to a greater value, up to the value &quot;10&quot;. At its maximum value the# system will use more CPU, longer cycles (and technically may introduce# more latency), and will tolerate less already expired keys still present# in the system. It's a tradeoff between memory, CPU and latency.## active-expire-effort 1############################# LAZY FREEING ##################################### Redis has two primitives to delete keys. One is called DEL and is a blocking# deletion of the object. It means that the server stops processing new commands# in order to reclaim all the memory associated with an object in a synchronous# way. If the key deleted is associated with a small object, the time needed# in order to execute the DEL command is very small and comparable to most other# O(1) or O(log_N) commands in Redis. However if the key is associated with an# aggregated value containing millions of elements, the server can block for# a long time (even seconds) in order to complete the operation.## For the above reasons Redis also offers non blocking deletion primitives# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and# FLUSHDB commands, in order to reclaim memory in background. Those commands# are executed in constant time. Another thread will incrementally free the# object in the background as fast as possible.## DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled.# It's up to the design of the application to understand when it is a good# idea to use one or the other. However the Redis server sometimes has to# delete keys or flush the whole database as a side effect of other operations.# Specifically Redis deletes objects independently of a user call in the# following scenarios:## 1) On eviction, because of the maxmemory and maxmemory policy configurations,#    in order to make room for new data, without going over the specified#    memory limit.# 2) Because of expire: when a key with an associated time to live (see the#    EXPIRE command) must be deleted from memory.# 3) Because of a side effect of a command that stores data on a key that may#    already exist. For example the RENAME command may delete the old key#    content when it is replaced with another one. Similarly SUNIONSTORE#    or SORT with STORE option may delete existing keys. The SET command#    itself removes any old content of the specified key in order to replace#    it with the specified string.# 4) During replication, when a replica performs a full resynchronization with#    its master, the content of the whole database is removed in order to#    load the RDB file just transferred.## In all the above cases the default is to delete objects in a blocking way,# like if DEL was called. However you can configure each case specifically# in order to instead release memory in a non-blocking way like if UNLINK# was called, using the following configuration directives.lazyfree-lazy-eviction nolazyfree-lazy-expire nolazyfree-lazy-server-del noreplica-lazy-flush no# It is also possible, for the case when to replace the user code DEL calls# with UNLINK calls is not easy, to modify the default behavior of the DEL# command to act exactly like UNLINK, using the following configuration# directive:lazyfree-lazy-user-del no# FLUSHDB, FLUSHALL, and SCRIPT FLUSH support both asynchronous and synchronous# deletion, which can be controlled by passing the [SYNC|ASYNC] flags into the# commands. When neither flag is passed, this directive will be used to determine# if the data should be deleted asynchronously.lazyfree-lazy-user-flush no################################ THREADED I/O ################################## Redis is mostly single threaded, however there are certain threaded# operations such as UNLINK, slow I/O accesses and other things that are# performed on side threads.## Now it is also possible to handle Redis clients socket reads and writes# in different I/O threads. Since especially writing is so slow, normally# Redis users use pipelining in order to speed up the Redis performances per# core, and spawn multiple instances in order to scale more. Using I/O# threads it is possible to easily speedup two times Redis without resorting# to pipelining nor sharding of the instance.## By default threading is disabled, we suggest enabling it only in machines# that have at least 4 or more cores, leaving at least one spare core.# Using more than 8 threads is unlikely to help much. We also recommend using# threaded I/O only if you actually have performance problems, with Redis# instances being able to use a quite big percentage of CPU time, otherwise# there is no point in using this feature.## So for instance if you have a four cores boxes, try to use 2 or 3 I/O# threads, if you have a 8 cores, try to use 6 threads. In order to# enable I/O threads use the following configuration directive:## io-threads 4## Setting io-threads to 1 will just use the main thread as usual.# When I/O threads are enabled, we only use threads for writes, that is# to thread the write(2) syscall and transfer the client buffers to the# socket. However it is also possible to enable threading of reads and# protocol parsing using the following configuration directive, by setting# it to yes:## io-threads-do-reads no## Usually threading reads doesn't help much.## NOTE 1: This configuration directive cannot be changed at runtime via# CONFIG SET. Aso this feature currently does not work when SSL is# enabled.## NOTE 2: If you want to test the Redis speedup using redis-benchmark, make# sure you also run the benchmark itself in threaded mode, using the# --threads option to match the number of Redis threads, otherwise you'll not# be able to notice the improvements.############################ KERNEL OOM CONTROL ############################### On Linux, it is possible to hint the kernel OOM killer on what processes# should be killed first when out of memory.## Enabling this feature makes Redis actively control the oom_score_adj value# for all its processes, depending on their role. The default scores will# attempt to have background child processes killed before all others, and# replicas killed before masters.## Redis supports three options:## no:       Don't make changes to oom-score-adj (default).# yes:      Alias to &quot;relative&quot; see below.# absolute: Values in oom-score-adj-values are written as is to the kernel.# relative: Values are used relative to the initial value of oom_score_adj when#           the server starts and are then clamped to a range of -1000 to 1000.#           Because typically the initial value is 0, they will often match the#           absolute values.oom-score-adj no# When oom-score-adj is used, this directive controls the specific values used# for master, replica and background child processes. Values range -2000 to# 2000 (higher means more likely to be killed).## Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities)# can freely increase their value, but not decrease it below its initial# settings. This means that setting oom-score-adj to &quot;relative&quot; and setting the# oom-score-adj-values to positive values will always succeed.oom-score-adj-values 0 200 800#################### KERNEL transparent hugepage CONTROL ####################### Usually the kernel Transparent Huge Pages control is set to &quot;madvise&quot; or# or &quot;never&quot; by default (/sys/kernel/mm/transparent_hugepage/enabled), in which# case this config has no effect. On systems in which it is set to &quot;always&quot;,# redis will attempt to disable it specifically for the redis process in order# to avoid latency problems specifically with fork(2) and CoW.# If for some reason you prefer to keep it enabled, you can set this config to# &quot;no&quot; and the kernel global to &quot;always&quot;.disable-thp yes############################## APPEND ONLY MODE ################################ By default Redis asynchronously dumps the dataset on disk. This mode is# good enough in many applications, but an issue with the Redis process or# a power outage may result into a few minutes of writes lost (depending on# the configured save points).## The Append Only File is an alternative persistence mode that provides# much better durability. For instance using the default data fsync policy# (see later in the config file) Redis can lose just one second of writes in a# dramatic event like a server power outage, or a single write if something# wrong with the Redis process itself happens, but the operating system is# still running correctly.## AOF and RDB persistence can be enabled at the same time without problems.# If the AOF is enabled on startup Redis will load the AOF, that is the file# with the better durability guarantees.## Please check https://redis.io/topics/persistence for more information.appendonly no# The name of the append only file (default: &quot;appendonly.aof&quot;)appendfilename &quot;appendonly.aof&quot;# The fsync() call tells the Operating System to actually write data on disk# instead of waiting for more data in the output buffer. Some OS will really flush# data on disk, some other OS will just try to do it ASAP.## Redis supports three different modes:## no: don't fsync, just let the OS flush the data when it wants. Faster.# always: fsync after every write to the append only log. Slow, Safest.# everysec: fsync only one time every second. Compromise.## The default is &quot;everysec&quot;, as that's usually the right compromise between# speed and data safety. It's up to you to understand if you can relax this to# &quot;no&quot; that will let the operating system flush the output buffer when# it wants, for better performances (but if you can live with the idea of# some data loss consider the default persistence mode that's snapshotting),# or on the contrary, use &quot;always&quot; that's very slow but a bit safer than# everysec.## More details please check the following article:# http://antirez.com/post/redis-persistence-demystified.html## If unsure, use &quot;everysec&quot;.# appendfsync alwaysappendfsync everysec# appendfsync no# When the AOF fsync policy is set to always or everysec, and a background# saving process (a background save or AOF log background rewriting) is# performing a lot of I/O against the disk, in some Linux configurations# Redis may block too long on the fsync() call. Note that there is no fix for# this currently, as even performing fsync in a different thread will block# our synchronous write(2) call.## In order to mitigate this problem it's possible to use the following option# that will prevent fsync() from being called in the main process while a# BGSAVE or BGREWRITEAOF is in progress.## This means that while another child is saving, the durability of Redis is# the same as &quot;appendfsync none&quot;. In practical terms, this means that it is# possible to lose up to 30 seconds of log in the worst scenario (with the# default Linux settings).## If you have latency problems turn this to &quot;yes&quot;. Otherwise leave it as# &quot;no&quot; that is the safest pick from the point of view of durability.no-appendfsync-on-rewrite no# Automatic rewrite of the append only file.# Redis is able to automatically rewrite the log file implicitly calling# BGREWRITEAOF when the AOF log size grows by the specified percentage.## This is how it works: Redis remembers the size of the AOF file after the# latest rewrite (if no rewrite has happened since the restart, the size of# the AOF at startup is used).## This base size is compared to the current size. If the current size is# bigger than the specified percentage, the rewrite is triggered. Also# you need to specify a minimal size for the AOF file to be rewritten, this# is useful to avoid rewriting the AOF file even if the percentage increase# is reached but it is still pretty small.## Specify a percentage of zero in order to disable the automatic AOF# rewrite feature.auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb# An AOF file may be found to be truncated at the end during the Redis# startup process, when the AOF data gets loaded back into memory.# This may happen when the system where Redis is running# crashes, especially when an ext4 filesystem is mounted without the# data=ordered option (however this can't happen when Redis itself# crashes or aborts but the operating system still works correctly).## Redis can either exit with an error when this happens, or load as much# data as possible (the default now) and start if the AOF file is found# to be truncated at the end. The following option controls this behavior.## If aof-load-truncated is set to yes, a truncated AOF file is loaded and# the Redis server starts emitting a log to inform the user of the event.# Otherwise if the option is set to no, the server aborts with an error# and refuses to start. When the option is set to no, the user requires# to fix the AOF file using the &quot;redis-check-aof&quot; utility before to restart# the server.## Note that if the AOF file will be found to be corrupted in the middle# the server will still exit with an error. This option only applies when# Redis will try to read more data from the AOF file but not enough bytes# will be found.aof-load-truncated yes# When rewriting the AOF file, Redis is able to use an RDB preamble in the# AOF file for faster rewrites and recoveries. When this option is turned# on the rewritten AOF file is composed of two different stanzas:##   [RDB file][AOF tail]## When loading, Redis recognizes that the AOF file starts with the &quot;REDIS&quot;# string and loads the prefixed RDB file, then continues loading the AOF# tail.aof-use-rdb-preamble yes################################ LUA SCRIPTING  ################################ Max execution time of a Lua script in milliseconds.## If the maximum execution time is reached Redis will log that a script is# still in execution after the maximum allowed time and will start to# reply to queries with an error.## When a long running script exceeds the maximum execution time only the# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be# used to stop a script that did not yet call any write commands. The second# is the only way to shut down the server in the case a write command was# already issued by the script but the user doesn't want to wait for the natural# termination of the script.## Set it to 0 or a negative value for unlimited execution without warnings.lua-time-limit 5000################################ REDIS CLUSTER  ################################ Normal Redis instances can't be part of a Redis Cluster; only nodes that are# started as cluster nodes can. In order to start a Redis instance as a# cluster node enable the cluster support uncommenting the following:## cluster-enabled yes# Every cluster node has a cluster configuration file. This file is not# intended to be edited by hand. It is created and updated by Redis nodes.# Every Redis Cluster node requires a different cluster configuration file.# Make sure that instances running in the same system do not have# overlapping cluster configuration file names.## cluster-config-file nodes-6379.conf# Cluster node timeout is the amount of milliseconds a node must be unreachable# for it to be considered in failure state.# Most other internal time limits are a multiple of the node timeout.## cluster-node-timeout 15000# A replica of a failing master will avoid to start a failover if its data# looks too old.## There is no simple way for a replica to actually have an exact measure of# its &quot;data age&quot;, so the following two checks are performed:## 1) If there are multiple replicas able to failover, they exchange messages#    in order to try to give an advantage to the replica with the best#    replication offset (more data from the master processed).#    Replicas will try to get their rank by offset, and apply to the start#    of the failover a delay proportional to their rank.## 2) Every single replica computes the time of the last interaction with#    its master. This can be the last ping or command received (if the master#    is still in the &quot;connected&quot; state), or the time that elapsed since the#    disconnection with the master (if the replication link is currently down).#    If the last interaction is too old, the replica will not try to failover#    at all.## The point &quot;2&quot; can be tuned by user. Specifically a replica will not perform# the failover if, since the last interaction with the master, the time# elapsed is greater than:##   (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period## So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the# replica will not try to failover if it was not able to talk with the master# for longer than 310 seconds.## A large cluster-replica-validity-factor may allow replicas with too old data to failover# a master, while a too small value may prevent the cluster from being able to# elect a replica at all.## For maximum availability, it is possible to set the cluster-replica-validity-factor# to a value of 0, which means, that replicas will always try to failover the# master regardless of the last time they interacted with the master.# (However they'll always try to apply a delay proportional to their# offset rank).## Zero is the only value able to guarantee that when all the partitions heal# the cluster will always be able to continue.## cluster-replica-validity-factor 10# Cluster replicas are able to migrate to orphaned masters, that are masters# that are left without working replicas. This improves the cluster ability# to resist to failures as otherwise an orphaned master can't be failed over# in case of failure if it has no working replicas.## Replicas migrate to orphaned masters only if there are still at least a# given number of other working replicas for their old master. This number# is the &quot;migration barrier&quot;. A migration barrier of 1 means that a replica# will migrate only if there is at least 1 other working replica for its master# and so forth. It usually reflects the number of replicas you want for every# master in your cluster.## Default is 1 (replicas migrate only if their masters remain with at least# one replica). To disable migration just set it to a very large value or# set cluster-allow-replica-migration to 'no'.# A value of 0 can be set but is useful only for debugging and dangerous# in production.## cluster-migration-barrier 1# Turning off this option allows to use less automatic cluster configuration.# It both disables migration to orphaned masters and migration from masters# that became empty.## Default is 'yes' (allow automatic migrations).## cluster-allow-replica-migration yes# By default Redis Cluster nodes stop accepting queries if they detect there# is at least a hash slot uncovered (no available node is serving it).# This way if the cluster is partially down (for example a range of hash slots# are no longer covered) all the cluster becomes, eventually, unavailable.# It automatically returns available as soon as all the slots are covered again.## However sometimes you want the subset of the cluster which is working,# to continue to accept queries for the part of the key space that is still# covered. In order to do so, just set the cluster-require-full-coverage# option to no.## cluster-require-full-coverage yes# This option, when set to yes, prevents replicas from trying to failover its# master during master failures. However the replica can still perform a# manual failover, if forced to do so.## This is useful in different scenarios, especially in the case of multiple# data center operations, where we want one side to never be promoted if not# in the case of a total DC failure.## cluster-replica-no-failover no# This option, when set to yes, allows nodes to serve read traffic while the# the cluster is in a down state, as long as it believes it owns the slots. ## This is useful for two cases.  The first case is for when an application # doesn't require consistency of data during node failures or network partitions.# One example of this is a cache, where as long as the node has the data it# should be able to serve it. ## The second use case is for configurations that don't meet the recommended  # three shards but want to enable cluster mode and scale later. A # master outage in a 1 or 2 shard configuration causes a read/write outage to the# entire cluster without this option set, with it set there is only a write outage.# Without a quorum of masters, slot ownership will not change automatically. ## cluster-allow-reads-when-down no# In order to setup your cluster make sure to read the documentation# available at https://redis.io web site.########################## CLUSTER DOCKER/NAT support  ######################### In certain deployments, Redis Cluster nodes address discovery fails, because# addresses are NAT-ted or because ports are forwarded (the typical case is# Docker and other containers).## In order to make Redis Cluster working in such environments, a static# configuration where each node knows its public address is needed. The# following four options are used for this scope, and are:## * cluster-announce-ip# * cluster-announce-port# * cluster-announce-tls-port# * cluster-announce-bus-port## Each instructs the node about its address, client ports (for connections# without and with TLS) and cluster message bus port. The information is then# published in the header of the bus packets so that other nodes will be able to# correctly map the address of the node publishing the information.## If cluster-tls is set to yes and cluster-announce-tls-port is omitted or set# to zero, then cluster-announce-port refers to the TLS port. Note also that# cluster-announce-tls-port has no effect if cluster-tls is set to no.## If the above options are not used, the normal Redis Cluster auto-detection# will be used instead.## Note that when remapped, the bus port may not be at the fixed offset of# clients port + 10000, so you can specify any port and bus-port depending# on how they get remapped. If the bus-port is not set, a fixed offset of# 10000 will be used as usual.## Example:## cluster-announce-ip 10.1.1.5# cluster-announce-tls-port 6379# cluster-announce-port 0# cluster-announce-bus-port 6380################################## SLOW LOG #################################### The Redis Slow Log is a system to log queries that exceeded a specified# execution time. The execution time does not include the I/O operations# like talking with the client, sending the reply and so forth,# but just the time needed to actually execute the command (this is the only# stage of command execution where the thread is blocked and can not serve# other requests in the meantime).## You can configure the slow log with two parameters: one tells Redis# what is the execution time, in microseconds, to exceed in order for the# command to get logged, and the other parameter is the length of the# slow log. When a new command is logged the oldest one is removed from the# queue of logged commands.# The following time is expressed in microseconds, so 1000000 is equivalent# to one second. Note that a negative number disables the slow log, while# a value of zero forces the logging of every command.slowlog-log-slower-than 10000# There is no limit to this length. Just be aware that it will consume memory.# You can reclaim memory used by the slow log with SLOWLOG RESET.slowlog-max-len 128################################ LATENCY MONITOR ############################### The Redis latency monitoring subsystem samples different operations# at runtime in order to collect data related to possible sources of# latency of a Redis instance.## Via the LATENCY command this information is available to the user that can# print graphs and obtain reports.## The system only logs operations that were performed in a time equal or# greater than the amount of milliseconds specified via the# latency-monitor-threshold configuration directive. When its value is set# to zero, the latency monitor is turned off.## By default latency monitoring is disabled since it is mostly not needed# if you don't have latency issues, and collecting data has a performance# impact, that while very small, can be measured under big load. Latency# monitoring can easily be enabled at runtime using the command# &quot;CONFIG SET latency-monitor-threshold &lt;milliseconds&gt;&quot; if needed.latency-monitor-threshold 0############################# EVENT NOTIFICATION ############################### Redis can notify Pub/Sub clients about events happening in the key space.# This feature is documented at https://redis.io/topics/notifications## For instance if keyspace events notification is enabled, and a client# performs a DEL operation on key &quot;foo&quot; stored in the Database 0, two# messages will be published via Pub/Sub:## PUBLISH __keyspace@0__:foo del# PUBLISH __keyevent@0__:del foo## It is possible to select the events that Redis will notify among a set# of classes. Every class is identified by a single character:##  K     Keyspace events, published with __keyspace@&lt;db&gt;__ prefix.#  E     Keyevent events, published with __keyevent@&lt;db&gt;__ prefix.#  g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...#  $     String commands#  l     List commands#  s     Set commands#  h     Hash commands#  z     Sorted set commands#  x     Expired events (events generated every time a key expires)#  e     Evicted events (events generated when a key is evicted for maxmemory)#  t     Stream commands#  d     Module key type events#  m     Key-miss events (Note: It is not included in the 'A' class)#  A     Alias for g$lshzxetd, so that the &quot;AKE&quot; string means all the events#        (Except key-miss events which are excluded from 'A' due to their#         unique nature).##  The &quot;notify-keyspace-events&quot; takes as argument a string that is composed#  of zero or multiple characters. The empty string means that notifications#  are disabled.##  Example: to enable list and generic events, from the point of view of the#           event name, use:##  notify-keyspace-events Elg##  Example 2: to get the stream of the expired keys subscribing to channel#             name __keyevent@0__:expired use:##  notify-keyspace-events Ex##  By default all notifications are disabled because most users don't need#  this feature and the feature has some overhead. Note that if you don't#  specify at least one of K or E, no events will be delivered.notify-keyspace-events &quot;&quot;############################### GOPHER SERVER ################################## Redis contains an implementation of the Gopher protocol, as specified in# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).## The Gopher protocol was very popular in the late '90s. It is an alternative# to the web, and the implementation both server and client side is so simple# that the Redis server has just 100 lines of code in order to implement this# support.## What do you do with Gopher nowadays? Well Gopher never *really* died, and# lately there is a movement in order for the Gopher more hierarchical content# composed of just plain text documents to be resurrected. Some want a simpler# internet, others believe that the mainstream internet became too much# controlled, and it's cool to create an alternative space for people that# want a bit of fresh air.## Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol# as a gift.## --- HOW IT WORKS? ---## The Redis Gopher support uses the inline protocol of Redis, and specifically# two kind of inline requests that were anyway illegal: an empty request# or any request that starts with &quot;/&quot; (there are no Redis commands starting# with such a slash). Normal RESP2/RESP3 requests are completely out of the# path of the Gopher protocol implementation and are served as usual as well.## If you open a connection to Redis when Gopher is enabled and send it# a string like &quot;/foo&quot;, if there is a key named &quot;/foo&quot; it is served via the# Gopher protocol.## In order to create a real Gopher &quot;hole&quot; (the name of a Gopher site in Gopher# talking), you likely need a script like the following:##   https://github.com/antirez/gopher2redis## --- SECURITY WARNING ---## If you plan to put Redis on the internet in a publicly accessible address# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.# Once a password is set:##   1. The Gopher server (when enabled, not by default) will still serve#      content via Gopher.#   2. However other commands cannot be called before the client will#      authenticate.## So use the 'requirepass' option to protect your instance.## Note that Gopher is not currently supported when 'io-threads-do-reads'# is enabled.## To enable Gopher support, uncomment the following line and set the option# from no (the default) to yes.## gopher-enabled no############################### ADVANCED CONFIG ################################ Hashes are encoded using a memory efficient data structure when they have a# small number of entries, and the biggest entry does not exceed a given# threshold. These thresholds can be configured using the following directives.hash-max-ziplist-entries 512hash-max-ziplist-value 64# Lists are also encoded in a special way to save a lot of space.# The number of entries allowed per internal list node can be specified# as a fixed maximum size or a maximum number of elements.# For a fixed maximum size, use -5 through -1, meaning:# -5: max size: 64 Kb  &lt;-- not recommended for normal workloads# -4: max size: 32 Kb  &lt;-- not recommended# -3: max size: 16 Kb  &lt;-- probably not recommended# -2: max size: 8 Kb   &lt;-- good# -1: max size: 4 Kb   &lt;-- good# Positive numbers mean store up to _exactly_ that number of elements# per list node.# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),# but if your use case is unique, adjust the settings as necessary.list-max-ziplist-size -2# Lists may also be compressed.# Compress depth is the number of quicklist ziplist nodes from *each* side of# the list to *exclude* from compression.  The head and tail of the list# are always uncompressed for fast push/pop operations.  Settings are:# 0: disable all list compression# 1: depth 1 means &quot;don't start compressing until after 1 node into the list,#    going from either the head or tail&quot;#    So: [head]-&gt;node-&gt;node-&gt;...-&gt;node-&gt;[tail]#    [head], [tail] will always be uncompressed; inner nodes will compress.# 2: [head]-&gt;[next]-&gt;node-&gt;node-&gt;...-&gt;node-&gt;[prev]-&gt;[tail]#    2 here means: don't compress head or head-&gt;next or tail-&gt;prev or tail,#    but compress all nodes between them.# 3: [head]-&gt;[next]-&gt;[next]-&gt;node-&gt;node-&gt;...-&gt;node-&gt;[prev]-&gt;[prev]-&gt;[tail]# etc.list-compress-depth 0# Sets have a special encoding in just one case: when a set is composed# of just strings that happen to be integers in radix 10 in the range# of 64 bit signed integers.# The following configuration setting sets the limit in the size of the# set in order to use this special memory saving encoding.set-max-intset-entries 512# Similarly to hashes and lists, sorted sets are also specially encoded in# order to save a lot of space. This encoding is only used when the length and# elements of a sorted set are below the following limits:zset-max-ziplist-entries 128zset-max-ziplist-value 64# HyperLogLog sparse representation bytes limit. The limit includes the# 16 bytes header. When an HyperLogLog using the sparse representation crosses# this limit, it is converted into the dense representation.## A value greater than 16000 is totally useless, since at that point the# dense representation is more memory efficient.## The suggested value is ~ 3000 in order to have the benefits of# the space efficient encoding without slowing down too much PFADD,# which is O(N) with the sparse encoding. The value can be raised to# ~ 10000 when CPU is not a concern, but space is, and the data set is# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.hll-sparse-max-bytes 3000# Streams macro node max size / items. The stream data structure is a radix# tree of big nodes that encode multiple items inside. Using this configuration# it is possible to configure how big a single node can be in bytes, and the# maximum number of items it may contain before switching to a new node when# appending new stream entries. If any of the following settings are set to# zero, the limit is ignored, so for instance it is possible to set just a# max entries limit by setting max-bytes to 0 and max-entries to the desired# value.stream-node-max-bytes 4096stream-node-max-entries 100# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in# order to help rehashing the main Redis hash table (the one mapping top-level# keys to values). The hash table implementation Redis uses (see dict.c)# performs a lazy rehashing: the more operation you run into a hash table# that is rehashing, the more rehashing &quot;steps&quot; are performed, so if the# server is idle the rehashing is never complete and some more memory is used# by the hash table.## The default is to use this millisecond 10 times every second in order to# actively rehash the main dictionaries, freeing memory when possible.## If unsure:# use &quot;activerehashing no&quot; if you have hard latency requirements and it is# not a good thing in your environment that Redis can reply from time to time# to queries with 2 milliseconds delay.## use &quot;activerehashing yes&quot; if you don't have such hard requirements but# want to free memory asap when possible.activerehashing yes# The client output buffer limits can be used to force disconnection of clients# that are not reading data from the server fast enough for some reason (a# common reason is that a Pub/Sub client can't consume messages as fast as the# publisher can produce them).## The limit can be set differently for the three different classes of clients:## normal -&gt; normal clients including MONITOR clients# replica  -&gt; replica clients# pubsub -&gt; clients subscribed to at least one pubsub channel or pattern## The syntax of every client-output-buffer-limit directive is the following:## client-output-buffer-limit &lt;class&gt; &lt;hard limit&gt; &lt;soft limit&gt; &lt;soft seconds&gt;## A client is immediately disconnected once the hard limit is reached, or if# the soft limit is reached and remains reached for the specified number of# seconds (continuously).# So for instance if the hard limit is 32 megabytes and the soft limit is# 16 megabytes / 10 seconds, the client will get disconnected immediately# if the size of the output buffers reach 32 megabytes, but will also get# disconnected if the client reaches 16 megabytes and continuously overcomes# the limit for 10 seconds.## By default normal clients are not limited because they don't receive data# without asking (in a push way), but just after a request, so only# asynchronous clients may create a scenario where data is requested faster# than it can read.## Instead there is a default limit for pubsub and replica clients, since# subscribers and replicas receive data in a push fashion.## Both the hard or the soft limit can be disabled by setting them to zero.client-output-buffer-limit normal 0 0 0client-output-buffer-limit replica 256mb 64mb 60client-output-buffer-limit pubsub 32mb 8mb 60# Client query buffers accumulate new commands. They are limited to a fixed# amount by default in order to avoid that a protocol desynchronization (for# instance due to a bug in the client) will lead to unbound memory usage in# the query buffer. However you can configure it here if you have very special# needs, such us huge multi/exec requests or alike.## client-query-buffer-limit 1gb# In the Redis protocol, bulk requests, that are, elements representing single# strings, are normally limited to 512 mb. However you can change this limit# here, but must be 1mb or greater## proto-max-bulk-len 512mb# Redis calls an internal function to perform many background tasks, like# closing connections of clients in timeout, purging expired keys that are# never requested, and so forth.## Not all tasks are performed with the same frequency, but Redis checks for# tasks to perform according to the specified &quot;hz&quot; value.## By default &quot;hz&quot; is set to 10. Raising the value will use more CPU when# Redis is idle, but at the same time will make Redis more responsive when# there are many keys expiring at the same time, and timeouts may be# handled with more precision.## The range is between 1 and 500, however a value over 100 is usually not# a good idea. Most users should use the default of 10 and raise this up to# 100 only in environments where very low latency is required.hz 10# Normally it is useful to have an HZ value which is proportional to the# number of clients connected. This is useful in order, for instance, to# avoid too many clients are processed for each background task invocation# in order to avoid latency spikes.## Since the default HZ value by default is conservatively set to 10, Redis# offers, and enables by default, the ability to use an adaptive HZ value# which will temporarily raise when there are many connected clients.## When dynamic HZ is enabled, the actual configured HZ will be used# as a baseline, but multiples of the configured HZ value will be actually# used as needed once more clients are connected. In this way an idle# instance will use very little CPU time while a busy instance will be# more responsive.dynamic-hz yes# When a child rewrites the AOF file, if the following option is enabled# the file will be fsync-ed every 32 MB of data generated. This is useful# in order to commit the file to the disk more incrementally and avoid# big latency spikes.aof-rewrite-incremental-fsync yes# When redis saves RDB file, if the following option is enabled# the file will be fsync-ed every 32 MB of data generated. This is useful# in order to commit the file to the disk more incrementally and avoid# big latency spikes.rdb-save-incremental-fsync yes# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good# idea to start with the default settings and only change them after investigating# how to improve the performances and how the keys LFU change over time, which# is possible to inspect via the OBJECT FREQ command.## There are two tunable parameters in the Redis LFU implementation: the# counter logarithm factor and the counter decay time. It is important to# understand what the two parameters mean before changing them.## The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis# uses a probabilistic increment with logarithmic behavior. Given the value# of the old counter, when a key is accessed, the counter is incremented in# this way:## 1. A random number R between 0 and 1 is extracted.# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1).# 3. The counter is incremented only if R &lt; P.## The default lfu-log-factor is 10. This is a table of how the frequency# counter changes with a different number of accesses with different# logarithmic factors:## +--------+------------+------------+------------+------------+------------+# | factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |# +--------+------------+------------+------------+------------+------------+# | 0      | 104        | 255        | 255        | 255        | 255        |# +--------+------------+------------+------------+------------+------------+# | 1      | 18         | 49         | 255        | 255        | 255        |# +--------+------------+------------+------------+------------+------------+# | 10     | 10         | 18         | 142        | 255        | 255        |# +--------+------------+------------+------------+------------+------------+# | 100    | 8          | 11         | 49         | 143        | 255        |# +--------+------------+------------+------------+------------+------------+## NOTE: The above table was obtained by running the following commands:##   redis-benchmark -n 1000000 incr foo#   redis-cli object freq foo## NOTE 2: The counter initial value is 5 in order to give new objects a chance# to accumulate hits.## The counter decay time is the time, in minutes, that must elapse in order# for the key counter to be divided by two (or decremented if it has a value# less &lt;= 10).## The default value for the lfu-decay-time is 1. A special value of 0 means to# decay the counter every time it happens to be scanned.## lfu-log-factor 10# lfu-decay-time 1########################### ACTIVE DEFRAGMENTATION ######################### What is active defragmentation?# -------------------------------## Active (online) defragmentation allows a Redis server to compact the# spaces left between small allocations and deallocations of data in memory,# thus allowing to reclaim back memory.## Fragmentation is a natural process that happens with every allocator (but# less so with Jemalloc, fortunately) and certain workloads. Normally a server# restart is needed in order to lower the fragmentation, or at least to flush# away all the data and create it again. However thanks to this feature# implemented by Oran Agra for Redis 4.0 this process can happen at runtime# in a &quot;hot&quot; way, while the server is running.## Basically when the fragmentation is over a certain level (see the# configuration options below) Redis will start to create new copies of the# values in contiguous memory regions by exploiting certain specific Jemalloc# features (in order to understand if an allocation is causing fragmentation# and to allocate it in a better place), and at the same time, will release the# old copies of the data. This process, repeated incrementally for all the keys# will cause the fragmentation to drop back to normal values.## Important things to understand:## 1. This feature is disabled by default, and only works if you compiled Redis#    to use the copy of Jemalloc we ship with the source code of Redis.#    This is the default with Linux builds.## 2. You never need to enable this feature if you don't have fragmentation#    issues.## 3. Once you experience fragmentation, you can enable this feature when#    needed with the command &quot;CONFIG SET activedefrag yes&quot;.## The configuration parameters are able to fine tune the behavior of the# defragmentation process. If you are not sure about what they mean it is# a good idea to leave the defaults untouched.# Enabled active defragmentation# activedefrag no# Minimum amount of fragmentation waste to start active defrag# active-defrag-ignore-bytes 100mb# Minimum percentage of fragmentation to start active defrag# active-defrag-threshold-lower 10# Maximum percentage of fragmentation at which we use maximum effort# active-defrag-threshold-upper 100# Minimal effort for defrag in CPU percentage, to be used when the lower# threshold is reached# active-defrag-cycle-min 1# Maximal effort for defrag in CPU percentage, to be used when the upper# threshold is reached# active-defrag-cycle-max 25# Maximum number of set/hash/zset/list fields that will be processed from# the main dictionary scan# active-defrag-max-scan-fields 1000# Jemalloc background thread for purging will be enabled by defaultjemalloc-bg-thread yes# It is possible to pin different threads and processes of Redis to specific# CPUs in your system, in order to maximize the performances of the server.# This is useful both in order to pin different Redis threads in different# CPUs, but also in order to make sure that multiple Redis instances running# in the same host will be pinned to different CPUs.## Normally you can do this using the &quot;taskset&quot; command, however it is also# possible to this via Redis configuration directly, both in Linux and FreeBSD.## You can pin the server/IO threads, bio threads, aof rewrite child process, and# the bgsave child process. The syntax to specify the cpu list is the same as# the taskset command:## Set redis server/io threads to cpu affinity 0,2,4,6:# server_cpulist 0-7:2## Set bio threads to cpu affinity 1,3:# bio_cpulist 1,3## Set aof rewrite child process to cpu affinity 8,9,10,11:# aof_rewrite_cpulist 8-11## Set bgsave child process to cpu affinity 1,10,11# bgsave_cpulist 1,10-11# In some cases redis will emit warnings and even refuse to start if it detects# that the system is in bad state, it is possible to suppress these warnings# by setting the following config which takes a space delimited list of warnings# to suppress## ignore-warnings ARM64-COW-BUG</code></pre><h1 id="5启动redis容器">5、启动Redis容器</h1><p>执行命令启动redis容器：</p><pre><code class="language-shell">docker-compose up -d</code></pre><p><img src="https://img.huangge1199.cn/blog/iRedisByDC/2022-04-24-17-10-52-image.png" alt="" /></p><h1 id="6远程连接验证结果">6、远程连接验证结果</h1><p>信息填完，点击OK</p><p><img src="https://img.huangge1199.cn/blog/iRedisByDC/2022-04-24-17-12-56-image.png" alt="" /></p><p>点击左侧对呀的连接，右侧出现redis服务器信息则为安装成功</p><p><img src="https://img.huangge1199.cn/blog/iRedisByDC/2022-04-24-17-14-15-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sun, 24 Apr 2022 16:53:43 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[docker-compose安装MySQL]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/docker-compose安装mysql</link>
                    <description>
                            <![CDATA[<p>docker中安装MySQL</p><p>本教程以MySQL5.7版本为例编写，如需其他版本，可自行前往docker hub网站查找对应的镜像，安装可能回和本教程有一定出入，清自行处理。<br />如遇问题也可以在评论中回复，本人会尽快给与回复</p><h1 id="1拉取镜像">1、拉取镜像</h1><pre><code class="language-shell">docker pull mysql:5.7</code></pre><p><img src="https://img.huangge1199.cn/blog/iMySQLByDC/img.png" alt="img.png" /></p><h1 id="2编写docker-composeyml文件">2、编写docker-compose.yml文件</h1><p>内容如下：</p><pre><code class="language-yaml">version: '3'services:    mysql:        container_name: mysql        image: mysql:5.7        environment:            - MYSQL_ROOT_PASSWORD=此处为root密码自行设置            - TZ=Asia/Shanghai        volumes:             - ./conf:/etc/mysql            - ./data:/var/lib/mysql            - ./init:/docker-entrypoint-initdb.d/        ports:             - 50010:3306        restart: always</code></pre><h1 id="3创建目录文件">3、创建目录文件</h1><p>根据docker-compose.yml文件创建对应目录文件</p><p><img src="https://img.huangge1199.cn/blog/iMySQLByDC/2022-04-24-16-20-33-image.png" alt="" /></p><h1 id="4编写mysql的配置文件">4、编写MySQL的配置文件</h1><p>在conf目录下创建my.cnf文件，文件内容如下：</p><pre><code>[mysqld]lower_case_table_names=1innodb_force_recovery = 0log-bin=/var/lib/mysql/mysql-binbinlog-format=ROWserver_id=1</code></pre><h1 id="5启动mysql容器">5、启动MySQL容器</h1><pre><code class="language-shell">docker-compose up -d</code></pre><p><img src="https://img.huangge1199.cn/blog/iMySQLByDC/2022-04-24-16-30-32-image.png" alt="" /></p><h1 id="6远程连接验证结果">6、远程连接验证结果</h1><p><img src="https://img.huangge1199.cn/blog/iMySQLByDC/2022-04-24-16-45-29-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sun, 24 Apr 2022 15:57:49 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[用docker-compose安装nginx]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/用docker-compose安装nginx</link>
                    <description>
                            <![CDATA[<p>docker中安装nginx</p><h1 id="1查找nginx镜像">1、查找nginx镜像</h1><p>通过<a href="https://hub.docker.com/">Docker Hub网站查询nginx镜像</a>，选择下面的官方镜像</p><p><img src="https://img.huangge1199.cn/blog/iNginxByDC/2022-04-21-00-46-47-image.png" alt="" /></p><h1 id="2下载镜像">2、下载镜像</h1><p>3.1页面点进去后在右上方有docker拉取命令</p><p><img src="https://img.huangge1199.cn/blog/iNginxByDC/2022-04-21-00-47-51-image.png" alt="" /></p><pre><code class="language-shell">docker pull nginx</code></pre><p><img src="https://img.huangge1199.cn/blog/iNginxByDC/2022-04-21-01-03-03-image.png" alt="" /></p><h1 id="3编写docker-composeyml">3、编写docker-compose.yml</h1><p>docker-compose.yml内容如下：</p><pre><code class="language-shell">version: '3'services:    nginx:         container_name: nginx  #生成的容器名        image: nginx:latest #镜像        environment:            - TZ=Asia/Shanghai #时间        volumes:             - ./html:/usr/share/nginx/html              #nginx静态页位置            - ./conf/nginx.conf:/etc/nginx/nginx.conf   #配置文件            - ./conf.d:/etc/nginx/conf.d                #配置文件            - ./logs:/var/log/nginx                     #日志        ports:             - 80:80            - 443:443        restart: always</code></pre><h1 id="4创建目录以及nginx配置文件">4、创建目录以及nginx配置文件</h1><p>根据docker-compose.yml建立文件目录，并编写相关文件</p><p>目录：</p><p><img src="https://img.huangge1199.cn/blog/iNginxByDC/2022-04-21-20-43-55-image.png" alt="" /></p><p>conf/nginx.conf：</p><pre><code>user  nginx;worker_processes  auto;error_log  /var/log/nginx/error.log notice;pid        /var/run/nginx.pid;events {    worker_connections  1024;}http {    include       /etc/nginx/mime.types;    default_type  application/octet-stream;    log_format  main  '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '                      '$status $body_bytes_sent &quot;$http_referer&quot; '                      '&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;';    access_log  /var/log/nginx/access.log  main;    sendfile        on;    #tcp_nopush     on;    keepalive_timeout  65;    #gzip  on;    include /etc/nginx/conf.d/*.conf;}</code></pre><p>conf.d/default.conf</p><pre><code>server {    listen       80;    listen  [::]:80;    server_name  localhost;    #access_log  /var/log/nginx/host.access.log  main;    location / {        root   /usr/share/nginx/html;        index  index.html index.htm;    }    #error_page  404              /404.html;    # redirect server error pages to the static page /50x.html    #    error_page   500 502 503 504  /50x.html;    location = /50x.html {        root   /usr/share/nginx/html;    }    # proxy the PHP scripts to Apache listening on 127.0.0.1:80    #    #location ~ \.php$ {    #    proxy_pass   http://127.0.0.1;    #}    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000    #    #location ~ \.php$ {    #    root           html;    #    fastcgi_pass   127.0.0.1:9000;    #    fastcgi_index  index.php;    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;    #    include        fastcgi_params;    #}    # deny access to .htaccess files, if Apache's document root    # concurs with nginx's one    #    #location ~ /\.ht {    #    deny  all;    #}}</code></pre><p>html/50x.html</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;title&gt;Error&lt;/title&gt;&lt;style&gt;html { color-scheme: light dark; }body { width: 35em; margin: 0 auto;font-family: Tahoma, Verdana, Arial, sans-serif; }&lt;/style&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;An error occurred.&lt;/h1&gt;&lt;p&gt;Sorry, the page you are looking for is currently unavailable.&lt;br/&gt;Please try again later.&lt;/p&gt;&lt;p&gt;If you are the system administrator of this resource then you should checkthe error log for details.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Faithfully yours, nginx.&lt;/em&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><p>html/index.html</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;title&gt;Welcome to nginx!&lt;/title&gt;&lt;style&gt;html { color-scheme: light dark; }body { width: 35em; margin: 0 auto;font-family: Tahoma, Verdana, Arial, sans-serif; }&lt;/style&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;&lt;p&gt;If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.&lt;/p&gt;&lt;p&gt;For online documentation and support please refer to&lt;a href=&quot;http://nginx.org/&quot;&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;Commercial support is available at&lt;a href=&quot;http://nginx.com/&quot;&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</code></pre><h1 id="5docker-compose启动nginx">5、docker-compose启动nginx</h1><pre><code class="language-shell">cd nginx/lldocker-compose up -d</code></pre><p><img src="https://img.huangge1199.cn/blog/iNginxByDC/2022-04-21-19-59-02-image.png" alt="" /></p><h1 id="6验证nginx正常启动">6、验证nginx正常启动</h1><p>执行命令：</p><pre><code class="language-shell">docker ps -a</code></pre><p><img src="https://img.huangge1199.cn/blog/iNginxByDC/2022-04-21-20-12-55-image.png" alt="" /></p><p>然后在浏览器中输入IP，出现欢迎界面，安装完成</p><p><img src="https://img.huangge1199.cn/blog/iNginxByDC/2022-04-24-15-34-45-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sun, 24 Apr 2022 15:36:52 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[docker-compose安装]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/docker-compose安装</link>
                    <description>
                            <![CDATA[<p>docker-compose安装</p><p>按照官方来即可，<a href="https://docs.docker.com/compose/install/">docker-compose安装文档</a></p><p>按照自己的系统来安装：</p><p><img src="https://img.huangge1199.cn/blog/dockerComposeInstall/2022-04-21-00-36-46-image.png" alt="" /></p><h1 id="1下载docker-compose">1、下载docker-compose</h1><p>下面两个二选一，建议国内源，速度快</p><p>官方：</p><pre><code class="language-shell">sudo curl -L &quot;https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose</code></pre><p>国内源：</p><pre><code class="language-shell">curl -L https://get.daocloud.io/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` &gt; /usr/local/bin/docker-compose</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerComposeInstall/2022-04-21-00-40-56-image.png" alt="" /></p><h1 id="2授予权限">2、授予权限</h1><pre><code class="language-shell">sudo chmod +x /usr/local/bin/docker-composesudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose</code></pre><h1 id="3验证">3、验证</h1><pre><code class="language-shell">docker-compose --version</code></pre><p>输入命令后，出现版本号，则为安装成功</p><p><img src="https://img.huangge1199.cn/blog/dockerComposeInstall/2022-04-21-00-44-14-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sun, 24 Apr 2022 15:12:03 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[docker安装]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/docker安装</link>
                    <description>
                            <![CDATA[<p>安装docker</p><p>这部分基本就是按照docker官网的来，<a href="https://docs.docker.com/engine/install/centos/">centos安装docker文档</a></p><h1 id="1卸载旧版本docker">1、卸载旧版本docker</h1><pre><code class="language-sh">yum remove docker \                  docker-client \                  docker-client-latest \                  docker-common \                  docker-latest \                  docker-latest-logrotate \                  docker-logrotate \                  docker-engine</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerInstall/2022-04-20-23-44-07-image.png" alt="" /></p><h1 id="2设置docker软件源">2、设置docker软件源</h1><p>下面官网软件源和阿里软件源二选一，个人建议用阿里的，国内的速度快</p><p>官网软件源 ：速度慢，可以考虑阿里的</p><pre><code class="language-shell">yum install -y yum-utilsyum-config-manager \    --add-repo \    https://download.docker.com/linux/centos/docker-ce.repo</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerInstall/2022-04-20-23-47-39-image.png" alt="" /></p><p>阿里软件源：</p><p><img src="https://img.huangge1199.cn/blog/dockerInstall/2022-04-21-00-00-59-image.png" alt="" /></p><h1 id="3安装docker">3、安装docker</h1><pre><code class="language-shell">yum install docker-ce docker-ce-cli containerd.io</code></pre><p>命令输入后，中途出现下面的内容，输入<code>y</code>，然后按回车确认</p><p><img src="https://img.huangge1199.cn/blog/dockerInstall/2022-04-20-23-50-06-image.png" alt="" /></p><p>中途出现下面的内容，输入<code>y</code>，然后按回车确认</p><p><img src="https://img.huangge1199.cn/blog/dockerInstall/2022-04-21-00-05-16-image.png" alt="" /></p><h1 id="4更改docker仓库地址用docker中国区官方替换掉要不之后拉取镜像速度太慢了">4、更改docker仓库地址，用Docker中国区官方替换掉，要不之后拉取镜像速度太慢了</h1><pre><code class="language-shell">vi /etc/docker/daemon.json</code></pre><p>daemon.json内容：</p><pre><code class="language-json">{ &quot;registry-mirrors&quot;: [&quot;https://registry.docker-cn.com&quot;]}</code></pre><h1 id="5启动docker">5、启动docker</h1><pre><code class="language-shell">systemctl start docker</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerInstall/2022-04-21-00-22-21-image.png" alt="" /></p><h1 id="6设置开机启动docker">6、设置开机启动docker</h1><pre><code class="language-shell">systemctl enable docker</code></pre><p><img src="https://img.huangge1199.cn/blog/dockerInstall/2022-04-21-00-22-43-image.png" alt="" /></p><h1 id="7验证">7、验证</h1><p>通过查询docker版本确认docker是否正常启动</p><pre><code class="language-shell">docker -v</code></pre><p>执行命令后正常显示docker版本则为安装启动成功</p>]]>
                    </description>
                    <pubDate>Sun, 24 Apr 2022 13:59:34 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣459:重复的子字符串]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣459重复的子字符串</link>
                    <description>
                            <![CDATA[<p>今天刷力扣发现一道有趣的题，这道题目很普通，但是解法确可以偷懒</p><p>原题链接：<a href="https://leetcode-cn.com/problems/repeated-substring-pattern/">力扣459:重复的子字符串</a></p><h1 id="题目">题目</h1><p>给定一个非空的字符串<meta charset="UTF-8" />&nbsp;<code>s</code>&nbsp;，检查是否可以通过由它的一个子串重复多次构成。</p><p>&nbsp;</p><p><strong>示例 1:</strong></p><pre><strong>输入:</strong> s = "abab"<strong>输出:</strong> true<strong>解释:</strong> 可由子串 "ab" 重复两次构成。</pre><p><strong>示例 2:</strong></p><pre><strong>输入:</strong> s = "aba"<strong>输出:</strong> false</pre><p><strong>示例 3:</strong></p><pre><strong>输入:</strong> s = "abcabcabcabc"<strong>输出:</strong> true<strong>解释:</strong> 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)</pre><p>&nbsp;</p><p><b>提示：</b></p><p><meta charset="UTF-8" /></p><ul><li><code>1 &lt;= s.length &lt;= 10<sup>4</sup></code></li><li><code>s</code>&nbsp;由小写英文字母组成</li></ul><div><div>Related Topics</div><div><li>字符串</li><li>字符串匹配</li></div></div><br><h1 id="个人解法">个人解法</h1><p>想法：既然要判断字符串是否由一个子串重复多次构成，那么如果结果是肯定的，这个字符串的长<br />度一定能够整除子串的长度。</p><p>所以我首先做一个循环，找到可能作为子串重复的字符串，在其基础上判断是否满足，循环结束<br />后都没有找到满足的，那么结果肯定就是false了。</p><p>接下来我们考虑循环内部的逻辑，如果一个子串可以满足子串重复多次组成当前的字符串，那么按<br />照子串的长度分割，每一部分都是相同的。接下来就是重点了！！！重点！！！怎么判断这些部分<br />都相同？？</p><div style="color: red">假设满足条件：<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s = "abdfs"<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parent = s1+s2+s3+s4+....sn（s1...sn都是s）<br/>根据上面的字符串以及子串作说明<br/>可以分为两步判断：<ol style="color: green"><li>s1和sn相同</li><li>s2s3s4...sn和s1s2s3....s(n-1)相同</li></ol>2中s2=s1，s3=s2.....sn=s(n-1)，这样一来s1,s2,s3....sn就都相同了</div><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public boolean repeatedSubstringPattern(String s) {        int lens = s.length();        for (int i = 1; i &lt; lens; i++) {            if (lens % i == 0) {                if (s.substring(0, i).equals(s.substring(lens - i))                        &amp;&amp; s.substring(i).equals(s.substring(0, lens - i))) {                    return true;                }            }        }        return false;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">class Solution:    def repeatedSubstringPattern(self, s: str) -&gt; bool:        for i in range(1, len(s)):            if len(s) % i == 0:                if s[0:i] == s[len(s)-i:len(s)] and s[0:len(s)-i] == s[i:len(s)]:                    return True        return False</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Mon, 18 Apr 2022 15:53:15 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣204:计数质数]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣204计数质数</link>
                    <description>
                            <![CDATA[<p>今天遇到一个有趣的题目，求小于给定非负整数的质数的数量</p><p>原题链接：<a href="https://leetcode-cn.com/problems/count-primes/">力扣204. 计数质数</a></p><h1 id="题目">题目</h1><p>给定整数 <code>n</code> ，返回 <em>所有小于非负整数 <code>n</code> 的质数的数量</em> 。</p><p> </p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>n = 10<strong>输出：</strong>4<strong>解释：</strong>小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>n = 0<strong>输出：</strong>0</pre><p><strong>示例 3：</strong></p><pre><strong>输入：</strong>n = 1<strong>输出</strong>：0</pre><p> </p><p><strong>提示：</strong></p><ul>    <li><code>0 <= n <= 5 * 10<sup>6</sup></code></li></ul><div><div>Related Topics</div><div><li>数组</li><li>数学</li><li>枚举</li><li>数论</li></div></div><h1 id="个人解法">个人解法</h1><p>思路：</p><p>这题我最开始想的比较简单，直接从0开始遍历到给定数字，遍历过程中判断是否是质数</p><p>java代码如下：</p><pre><code class="language-java">class Solution {    public int countPrimes(int n) {        if (n &lt;= 2) {            return 0;        }        int count = 1;        for (int i = 3; i &lt; n; i++) {            if (isPrime(i)) {                count++;            }        }        return count;    }    /**     * 判断是否是质数     *     * @param num 数字     * @return true：质数、false：不是质数     */    private boolean isPrime(int num) {        if (num &lt; 2) {            return false;        }        if (num == 2) {            return true;        }        for (int i = 2; i * i &lt;= num; i++) {            if (num % i == 0) {                return false;            }        }        return true;    }}</code></pre><p>这种办法虽然例子过了，但是最后提交时却是超时了</p><p>接下来，我又仔细的想了想，之后想到了一种办法，通过了，然后看了看题解，发现这完全就是埃拉托斯特<br />尼筛法，简称埃氏筛，也称素数筛，是一种简单且历史悠久的筛法，用来找出一定范围内所有的素数。</p><p>这种算法就是给出要筛数值的范围n，从2开始遍历直到  $\sqrt{2}$ 。从2开始把小于n并且是其倍数的标记上，<br />然后，按顺序是3，和2一样的步骤，不过要判断下是否被标记过，因为，被标记的不是质数</p><p>我在维基百科上看到了这个小动画，就是这个算法的整体步骤了</p><iframe frameborder=0 border=0 height=369 width=445 src="https://img.huangge1199.cn/blog/leetcode204/Sieve_of_Eratosthenes_animation.gif"></iframe><p>下面是我的java代码：</p><pre><code class="language-java">class Solution {    public int countPrimes(int n) {        if (n &lt;= 2) {            return 0;        }        boolean[] nums = new boolean[n + 1];        Arrays.fill(nums, true);        nums[0] = false;        nums[1] = false;        int count = 0;        int max = (int) Math.sqrt(n);        for (int i = 2; i &lt; n; i++) {            if (nums[i]) {                count++;                if (i &gt; max) {                    continue;                }                for (int j = i; j * i &lt; n; j++) {                    nums[j * i] = false;                }            }        }        return count;    }}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 12 Apr 2022 11:04:35 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[darwin是什么？]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/darwin是什么</link>
                    <description>
                            <![CDATA[<p>今天，在学习NPS时，看到服务端启动命令时，它的分类是linux|darwin和windows两种，之前没有见过darwin，实在是好奇。<br />通过网络的查找，学习到了以下知识：</p><ul><li>Darwin 是一个由苹果公司（Apple Inc.）开发的 UNIX 操作系统</li><li>自2000年后，Darwin 是苹果所有操作系统的基础，包括 macOS（原名 Mac OS X ，后缩写为 OS X，至 WWDC 2016 改名为 macOS）、iOS、watchOS 和 tvOS。</li><li>Darwin是xnu架构的实现，基本可以视作Mac的命令行部分。而xnu是乔布斯结合mach和bsd做出来的操作系统架构，是他被踢出苹果，自己开next公司时发明的，当时叫nextstep，后来被买回苹果</li></ul>]]>
                    </description>
                    <pubDate>Wed, 30 Mar 2022 09:16:32 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Autowired注解警告的解决办法]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/autowired注解警告的解决办法</link>
                    <description>
                            <![CDATA[<h1 id="autowired-在idea报警告">@AutoWired 在idea报警告</h1><p>近期，发现@AutoWired注解在idea中总是报警告</p><h2 id="java代码">java代码</h2><p>如下：</p><pre><code class="language-java">@Controllerpublic class UserController {    @Autowired    private UserService userService;}</code></pre><h2 id="警告内容">警告内容</h2><p>如下：</p><p><img src="https://img.huangge1199.cn/blog/autowiredWaring/2022-03-28-11-30-49-1648438205(1).png" alt="" /></p><h2 id="解决办法">解决办法</h2><p>于是乎，关联性的在网上找了找资料，用以下的写法不会报警告，同时这种写法也是spring官方推荐的写法，代码如下：</p><pre><code class="language-java">@Controllerpublic class UserController {    private final UserService userService;    public UserController(UserService userService){        this.userService = userService;    }}</code></pre><h2 id="lombok优雅写法">Lombok优雅写法</h2><pre><code class="language-java">@Controller@RequiredArgsConstructor(onConstructor = @__(@Autowired))public clas UserController {    //这里必须是final,若不使用final,用@NotNull注解也是可以的    private final UserService userService;}</code></pre><h1 id="拓展学习">拓展学习</h1><p>由此，我这边拓展到了spring的三种依赖注入方式：</p><ul><li><p>Field Injection</p></li><li><p>Constructor Injection</p></li><li><p>Setter Injection</p></li></ul><h2 id="field-injection">Field Injection</h2><p><code>@Autowired</code>注解的一大使用场景就是<code>Field Injection</code>。</p><p>具体形式如下：</p><pre><code class="language-java">@Controllerpublic class UserController {    @Autowired    private UserService userService;}</code></pre><p>这种注入方式通过Java的反射机制实现，所以private的成员也可以被注入具体的对象。</p><h2 id="constructor-injection">Constructor Injection</h2><p><code>Constructor Injection</code>是构造器注入，是我们日常最为推荐的一种使用方式。</p><p>具体形式如下：</p><pre><code class="language-java">@Controllerpublic class UserController {    private final UserService userService;    public UserController(UserService userService){        this.userService = userService;    }}</code></pre><p>这种注入方式很直接，通过对象构建的时候建立关系，所以这种方式对对象创建的顺序会有要求，当然Spring会为你搞定这样的先后顺序，除非你出现循环依赖，然后就会抛出异常。</p><h2 id="setter-injection">Setter Injection</h2><p><code>Setter Injection</code>也会用到<code>@Autowired</code>注解，但使用方式与<code>Field Injection</code>有所不同，<code>Field Injection</code>是用在成员变量上，而<code>Setter Injection</code>的时候，是用在成员变量的Setter函数上。</p><p>具体形式如下：</p><pre><code class="language-java">@Controllerpublic class UserController {    private UserService userService;    @Autowired    public void setUserService(UserService userService){        this.userService = userService;    }}</code></pre><p>这种注入方式也很好理解，就是通过调用成员变量的set方法来注入想要使用的依赖对象。</p><h2 id="三种依赖注入方式比较">三种依赖注入方式比较</h2><table><thead><tr><th>注入方式</th><th>可靠性</th><th>可维护性</th><th>可测试性</th><th>灵活性</th><th>循环关系的检测</th><th>性能影响</th></tr></thead><tbody><tr><td>Field</td><td>不可靠</td><td>低</td><td>差</td><td>很灵活</td><td>不检测</td><td>启动快</td></tr><tr><td>Constructor</td><td>可靠</td><td>高</td><td>好</td><td>不灵活</td><td>自动检测</td><td>启动慢</td></tr><tr><td>Setter</td><td>不可靠</td><td>低</td><td>好</td><td>很灵活</td><td>不检测</td><td>启动快</td></tr></tbody></table><h1 id="参考">参考：</h1><ol><li><p><a href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-constructor-injection">https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-constructor-injection</a></p></li><li><p><a href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-setter-injection">https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-setter-injection</a></p></li><li><p><a href="https://blog.csdn.net/weixin_43203497/article/details/104193350">利用Lombok编写优雅的spring依赖注入代码,去掉繁人的@Autowired_路遥知码农的博客-CSDN博客_lombok 依赖注入</a></p></li><li><p><a href="https://segmentfault.com/a/1190000040914633">https://segmentfault.com/a/1190000040914633</a></p></li></ol>]]>
                    </description>
                    <pubDate>Mon, 28 Mar 2022 11:20:43 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[influxdb安装（centos7）]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/influxdb安装centos7</link>
                    <description>
                            <![CDATA[<h1 id="1获取安装包">1、获取安装包</h1><pre><code class="language-sh">wget https://dl.influxdata.com/influxdb/releases/influxdb-1.8.10.x86_64.rpm</code></pre><p><img src="https://img.huangge1199.cn/blog/influxdbInstall/2022-03-12-18-28-41-image.png" alt="" /></p><h1 id="2安装">2、安装</h1><pre><code class="language-sh">yum localinstall influxdb-1.8.10.x86_64.rpm</code></pre><h1 id="3配置">3、配置</h1><pre><code class="language-shell">vim /etc/influxdb/influxdb.conf</code></pre><p>用户名密码（非必须）</p><p><img src="https://img.huangge1199.cn/blog/influxdbInstall/2022-03-12-18-41-44-image.png" alt="" /></p><p>开启influx功能</p><p><img src="https://img.huangge1199.cn/blog/influxdbInstall/2022-03-12-18-42-25-image.png" alt="" /></p><h1 id="4启动服务">4、启动服务</h1><pre><code class="language-shell">systemctl start influxdb</code></pre><h1 id="5启动">5、启动</h1><pre><code class="language-shell">influx</code></pre><p>在客户端工具窗口中执行以下语句设置用户名和密码（非必须）：</p><pre><code class="language-shell"># 创建管理员权限的用户CREATE USER root WITH PASSWORD 'root' WITH ALL PRIVILEGES</code></pre><h1 id="6验证">6、验证</h1><p>用其他机器远程连接：</p><pre><code class="language-shell">influx -host ip地址 -port 端口号</code></pre><p><img src="https://img.huangge1199.cn/blog/influxdbInstall/2022-03-12-18-54-58-image.png" alt="" /></p><p>这里创建数据库时报错，是因为我这边配置了用户名和密码，需要连接时带上用户名和密码才行</p><pre><code class="language-shell">iinflux -host ip地址 -port 端口号 -username 用户名 -password 密码</code></pre><p><img src="https://img.huangge1199.cn/blog/influxdbInstall/2022-03-12-18-56-23-image.png" alt="" /></p>]]>
                    </description>
                    <pubDate>Sat, 12 Mar 2022 18:14:53 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣590:N 叉树的后序遍历]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣590n叉树的后序遍历</link>
                    <description>
                            <![CDATA[<p>2022年03月12日 力扣每日一题</p><h1 id="题目">题目</h1><p>给定一个 n&nbsp;叉树的根节点<meta charset="UTF-8" />&nbsp;<code>root</code>&nbsp;，返回 <em>其节点值的<strong> 后序遍历</strong></em> 。</p><p>n 叉树 在输入中按层序遍历进行序列化表示，每组子节点由空值 <code>null</code> 分隔（请参见示例）。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><p><img src="https://assets.leetcode.com/uploads/2018/10/12/narytreeexample.png" style="height: 193px; width: 300px;" /></p><pre><strong>输入：</strong>root = [1,null,3,2,4,null,5,6]<strong>输出：</strong>[5,6,3,2,4,1]</pre><p><strong>示例 2：</strong></p><p><img alt="" src="https://assets.leetcode.com/uploads/2019/11/08/sample_4_964.png" style="height: 269px; width: 296px;" /></p><pre><strong>输入：</strong>root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]<strong>输出：</strong>[2,6,14,11,7,3,12,8,4,13,9,10,5,1]</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li>节点总数在范围 <code>[0, 10<sup>4</sup>]</code> 内</li><li><code>0 &lt;= Node.val &lt;= 10<sup>4</sup></code></li><li>n 叉树的高度小于或等于 <code>1000</code></li></ul><p>&nbsp;</p><p><strong>进阶：</strong>递归法很简单，你可以使用迭代法完成此题吗?</p><div><div>Related Topics</div><div><li>栈</li><li>树</li><li>深度优先搜索</li></div></div><h1 id="个人解法">个人解法</h1><p>思路：</p><p>　　这题简单，只需要递归做就好了，对于每一个节点，先存叶子节点，然后存根节点</p><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">import java.util.ArrayList;import java.util.List;/*// Definition for a Node.class Node {    public int val;    public List&lt;Node&gt; children;    public Node() {}    public Node(int _val) {        val = _val;    }    public Node(int _val, List&lt;Node&gt; _children) {        val = _val;        children = _children;    }};*/class Solution {    public List&lt;Integer&gt; postorder(Node root) {        list = new ArrayList&lt;&gt;();        dfs(root);        return list;    }    List&lt;Integer&gt; list;    private void dfs(Node root) {        if (root == null) {            return;        }        if (root.children.size() == 0) {            list.add(root.val);            return;        }        for (Node node : root.children) {            dfs(node);        }        list.add(root.val);    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">&quot;&quot;&quot;# Definition for a Node.class Node:    def __init__(self, val=None, children=None):        self.val = val        self.children = children&quot;&quot;&quot;from typing import Listclass Solution:    def postorder(self, root: 'Node') -&gt; List[int]:        arr = []        def dfs(root1: 'Node'):            if root1 is None:                return            if len(root1.children)==0:                arr.append(root1.val)                return            for node in root1.children:                dfs(node)            arr.append(root1.val)        dfs(root)        return arr</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Sat, 12 Mar 2022 10:10:45 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[推理界的3月11号]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/推理界的3月11号</link>
                    <description>
                            <![CDATA[<p>今天是3月11日，在推理界，今天在历史上的意义：</p><ul><li>《东方杂志》（中国）第一期开始连载连载《毒美人》118周年</li><li>小说林社（中国）出版《福尔摩斯再生案》第一册118周年</li><li>克里斯蒂安娜·布兰德（英国）诞辰115周年</li><li>梦野久作（日本）逝世86周年</li><li>厄尔·斯坦利·加德纳（美国）逝世52周年</li><li>弗瑞德里克·布朗（美国）逝世50周年</li></ul><h1 id="克里斯蒂安娜布兰德">克里斯蒂安娜·布兰德</h1><p>　　克里斯蒂安娜·布兰德（Christianna Brand，1907.3.11－1988.12.17），英国侦探小说作家，儿童文学作家。</p><p>　　克里斯蒂安娜·布兰德1907年出生于马来亚，原名为玛丽·克里斯蒂安娜·刘易斯（Mary Christianna Lewis），早年在印度生活。她从事过很多工作，包括模特、舞蹈演员、店员和家庭教师。</p><p>　　1941年，她创作了第一本以查尔斯·沃斯为主角的侦探小说《高跟鞋之死》（Death in High Heels），当时她还只是一个销售员。同年，她笔下的英国著名探长考克瑞尔在《晕头转向》（Heads You Lose）一书中初次登场，之后考克瑞尔先后七次出现在布兰德的作品中，考克瑞尔探长是她塑造最成功的侦探形象，以他为主角的侦探小说《绿色危机》（Green for Danger）也是布兰德最有名的小说。这部作品描写的是二次大战中一所医院中发生的故事，一名邮递员被送往手术室，不料却因麻醉过度而死。考克瑞尔探长亲自赶来调查，却不料护士长玛丽恩·贝茨也惨遭杀害……《绿色危机》自1944年出版之后，至今仍不断再版。1946年，《绿色危机》被Eagle－Lion公司拍成电影，由阿拉斯泰尔·希姆饰演探长，获得巨大成功。</p><p>　　由于《绿色危机》的成功，1946年，克里斯蒂安娜·布兰德加入了英国侦探作家俱乐部，自此她的创作生涯一发不可收拾，接连发表了多部小说。</p><p>　　上世纪50年代末开始，克里斯蒂安娜·布兰德开始专注于撰写各种不同类型的作品和短篇小说。她曾获得三次埃德加奖提名：短篇小说《杯中的毒药》（Poison in the Cup）（1969年2月，《埃勒里·奎因神秘杂志》）、《Twist for Twist》（1967年5月，《埃勒里·奎因神秘杂志》）以及一个有关苏格兰谋杀案的《天堂知道谁》（Heaven Knows Who）（1960年）。</p><p>　　1972到1973年间，克里斯蒂安娜·布兰德以其杰出的成就，被推选为英国犯罪作家协会主席。</p><p>　　克里斯蒂安娜·布兰德曾经使用过的笔名还有玛丽·安·阿希、安娜贝尔·琼斯、玛丽·罗兰和查娜·汤姆森。她的作品被称为“黄金时代最后的侦探小说”，克里斯蒂安娜·布兰德的作品善于在活泼、幽默的情节以及吸引人的诡计中寻求平衡，她在1988年去世，享年81岁。</p><h1 id="梦野久作">梦野久作</h1><p>　　日本著名幻想文学作家、变格派推理大师。</p><p>　　本名杉山直树，后改名杉山泰道。</p><p>　　曾用笔名有海若蓝平、香俱土三鸟、土原耕作、萌圆、杉山萌圆、沙门萌圆、萌圆山人、萌圆生、萌圆泰道、朴平、白木朴平、三鸟山人、香椎村人、青杉居士、外人某氏、钝骨生、TS生、T生等。</p><p>　　一九二六年，梦野久作在《妖鼓》投稿前，曾拿给父亲过目，父亲看过后说“就像梦野久作所写的小说&quot;。 所谓“梦野久作”是博多地区的方言，意指精神恍惚、成天做白日梦的人。曾有数十个笔名的他自此以后便固定使用了这四个字为其笔名。</p><p>　　梦野久作有“妖怪作家”之称，其所属的“变格派”讲究人性的怪奇、丑恶、战栗心理的唯美面，使得推理小说充满了文学艺术气息。其代表作《脑髓地狱》（1935年）被称为日本推理小说的四大奇书之一。</p><h2 id="生平年表">生平年表</h2><p>　　一八八九年一月四日生于九州福冈市。父亲杉山茂丸是右派教父、玄洋社头目头山满的盟友。直树出生後就由祖父母养育。</p><p>　　一八九一年开始学习四书诵读。亲生母亲与父亲离婚另嫁高桥家。</p><p>　　一八九二年开始学习能乐。熟读四书，遂有神童之称。</p><p>　　一八九五年，进入小学就读，身体虚弱瘦小，多由祖父教授学习。求知欲旺盛，具有绘画方面的天赋。</p><p>　　一八九九年，大名寻常小学毕业。进入高等小学就学。</p><p>　　一九零二年三月二十日，祖父因中风并发肺炎去世。</p><p>　　一九零三年三月，高等小学毕业，四月进入福冈县立中学修猷馆就读。</p><p>　　一九零八年三月福冈县立修猷馆中学毕业，十二月一日，以一年志愿兵身份入近卫步兵第一连队，在部队中担任小队长，颇受士兵们的信赖。</p><p>　　一九一零年，退伍之后，进入中央大学附属补习班，准备入学考。<br />　　<br />　　一九一一年，进入庆应大学文科系就读。</p><p>　　一九一二年，同父异母之弟五郎去世。二月二十六日，奉命成为陆军步兵少尉。十一月八日，继祖母去世。</p><p>　　一九一三年，因为弟弟的猝死，父亲遂令其从庆应大学休学。三月时，依父亲之命前往福冈县糟屋郡香椎村唐原经营果园。</p><p>　　一九一五年，于东京本乡的喜福寺剃发为僧。将直树改名为泰道。</p><p>　　一九一六年，以行脚僧身份从京都走到吉野山。</p><p>　　一九一七年被父亲叫回农园，还俗，继承杉山家业。从本年起，在父亲所组织的右派团体台华社机关杂志《黑白》发表有关谣曲与时事的评论之外，还撰写小说。这段期间使用的笔名有沙门萌圆、杉山萌圆、萌圆泰道等。</p><p>　　一九一八年二月二十五日，与镰田昌一的女儿阿仓结婚。连载《冀望日本青年》等。</p><p>　　一九一九年，长男龙丸出生。成为九州岛日报记者，开始于家庭专栏发表童话至一九二六年。</p><p>　　一九二零年，三十一岁，在父亲所投资之九州日报社当社会新闻记者，一九二二年在该报家庭版陆续发表童谣，所使用的笔名有梅若蓝平、香具上三鸟、上原耕作、三鸟山人等。并以萌圆泰道之笔名，将《吴井娘次》改名为《蜡人偶》连载。</p><p>　　一九二一年，移居福冈市荒户町。次男铁儿出生。</p><p>　　一九二二年，以杉山萌圆为笔名出版《白发小僧》长篇童话集。</p><p>　　一九二三年，九月因关东大地震，以九州岛日报社震灾特派记者身份发表《火烧后细见记》与《东京震灾素描》。</p><p>　　一九二四年，辞去九州岛日报社的工作，十月，以杉山泰道名义之《侏儒》，应徵博文馆的推理小说征选活动，获得佳作奖（没出版）。</p><p>　　一九二五年四月，再度任职九州日报社。三男参绿出生。</p><p>　　一九二六年是梦野久作生涯的转换年，正月开始撰写《脑髓地狱》初稿《狂人的解放治疗》，五月辞去报社工作，十月以初次使用梦野久作之笔名，《新青年》之侦探小说徵文之《妖鼓》，入选二等奖（没有一等奖），由此篇被公认之迟来的处女作，梦野久作登上推理文坛。其笔名是取自福冈博多地区的方言，指精神恍惚，经常寻找梦幻的人。</p><p>　　一九二七年二月，停止创作《狂人的解放治疗》初稿，创作短篇连载《乡村事件》。</p><p>　　一九二八年，陆续发表《人脸》、《死后之恋》、《瓶装地狱》等。</p><p>　　一九二九年，出版《梦野久作集》。陆续发表《押绘的奇迹》、《铁锤》、《飞翔于空中的洋伞》。</p><p>　　一九三零年五月，奉命担任妻子老家的福冈市黑门邮局局长。陆续发表《复仇》、《童贞》。</p><p>　　一九三一年，陆续发表《椰果》、《犬神博士》、《自白心理》等。</p><p>　　一九三二年，出版《押绘的奇迹》。陆续发表《斜坑》、《幽灵与推进机》、《狂气地狱》。</p><p>　　一九三三年，一月出版《暗黑公使》，四月出版《冰涯》，五月出版《瓶装地狱》，陆续发表《不冒烟的烟囱》、《爆弹太平记》、《白菊》等。</p><p>　　一九三四年，八月辞去黑门邮局局长一职。陆续发表《名君臣之》、《山羊胡编辑长》、《难船小僧》、《杀人直播》、《木魂》、《少女地狱》等。</p><p>　　一九三五年，一月出版《脑髓地狱》。三月出版《梅津只园翁传》。七月十九日，父亲茂丸因脑溢血猝死于曲町自宅（享年七十二岁）。十月，借帮父亲举行葬礼之便，携妻子至日本各地旅行。十二月，出版《近世快人传》。陆续发表《微笑哑女》、《超人胡夜博士》、《二重心脏》。</p><p>　　一九三六年，二月上京整理父亲遗物，遭遇“二二六&quot;事件。陆续发表《人肉香肠》、《恶魔祈祷书》。三月出版《少女地狱》，十一日与访客谈话中猝死于东京（死因不详），得年四十七岁。</p><p>　　梦野久作有“妖怪作家”之称，其所属的“变格派”讲究人性的怪奇、丑恶、战栗心理的唯美面，使得推理小说充满了文学艺术气息。其代表作《脑髓地狱》（1935年）被称为日本推理小说的四大奇书之一。</p><h1 id="厄尔斯坦利加德纳">厄尔·斯坦利·加德纳</h1><p>Erle Stanley Gardner（1889年7月17日美国马萨诸塞州马尔登-1970年3月11日加州Temecula）</p><p>其他署名:</p><ul><li>yle Corning</li><li>A. A. Fair</li><li>Charles M. Green</li><li>Grant Holiday</li><li>Carleton Kendrake</li><li>Charles J. Kenny</li><li>Robert Parr</li><li>Dane Rigley</li><li>Arthur Mann Sellers</li><li>harles M. Stanton</li><li>Les Tillray</li></ul><p>类型：</p><ul><li>大侦探</li><li>私人侦探</li><li>硬汉</li><li>法庭</li></ul><p>主要系列：</p><ul><li>佩里·梅森Perry Mason, 1933-1973</li><li>Doug Selby, D.A., 1937-1949</li><li>Bertha Cool and Donald Lam, 1939-1970</li></ul><p>　　加德纳是查尔斯·华尔特人·加德纳（Charles Walter Gardner）和格蕾丝·阿德尔玛·加德纳（Grace Adelma Gardner）之子。他的父亲是一位工程师，因为工作需要到处出差，他将全家搬到西海岸，在加德纳十岁的时候先是搬到了俄勒冈州，1902年又搬到加州奥维尔（Oroville）。加德纳对加州十分喜欢，虽然成年之后他游历四方，但是他还是将加州作为自己的家，并且作为自己笔下人物的背景。</p><p>　　加德纳个性独立，勤奋，有想象力，二十一岁时他成为了一名律师，但是他没有进入法律学校而是在律师事务所自学以及担任律师助手，最后通过律师考试。他在洛杉矶西北部的文图拉县开业，很快他因为精明、足智多谋而赢得了声誉，他帮助很多看似不可能打赢官司的委托人获胜。</p><p>　　加德纳喜欢户外活动，比如打猎，钓鱼、射箭，他成为作家之前他试过许多不同的生意，三十四岁的时候，他将自己的第一篇小说卖给了一家廉价杂志。他并不是一位有天赋的作家，但是他通过研究那些成功作家的作品以及编辑的意见而进步神速。二三十年代，他为廉价杂志创作了大量短篇小说，并且塑造了一大堆人物。1933年，他出版了第一部长篇小说，主角便是日后著名律师侦探佩里·梅森。那时，他创作速度惊人，每三天便能完成一部一万单词的中篇小说，以至于他无法依靠打自己要而使用口述机，因此他雇请了几位秘书，轮班根据他的口述完成稿件。</p><p>　　二次大战之后，加德纳的名声让他的创作数量减少了，因为他涉足其他的事务，包括“最高上诉法院”（Court of Last Resort），这是加德纳和他人创立的一家组织，主要是为了增加美国法律的公正性，还有佩里·梅森系列电视片。</p><p>　　1912年，加德纳与纳塔利·塔伯特（Natalie Talbert）结婚，1913年他们生下女儿纳塔利·格蕾丝·加德纳（Natalie Grace Gardner）。1935年，两人分居，不过他们还是朋友，也并未离婚。加德纳一直赡养他的妻子，直到1968年妻子去世。同年，加德纳与他长期以来的秘书艾格尼斯·简·贝斯尔（Agnes Jean Bethell）结婚，贝斯尔被认为是梅森的秘书德拉·斯特里特（Della Street）的原型。1962年，他获得美国侦探作家协会（MWA）大师奖。1970年，加德纳因为癌症去世。</p><h1 id="弗瑞德里克布朗">弗瑞德里克·布朗</h1><p>　　弗瑞德里克·布朗：Fredric Brown（1906年10月29日美国俄亥俄州辛辛那提-1972年3月11日亚利桑那州图森）</p><p>　　类型：私人侦探；硬汉</p><p>　　主要系列：Ed and Am Hunter, 1947-1963</p><p>　　布朗十多岁的时候父母相继去世，不得不自谋生路。二十年代，他进入汉诺威学院（Hanover College）和辛辛那提大学（Cincinnati University）学习。他于1929年结婚，并且搬到威斯康星州密尔沃基，在那里他为几家出版社担任校对，直到在《密尔沃基期刊》（Milwaukee Journal）找到一份固定的工作。他呆在这家杂志一直到1947年，接着搬到纽约，在一家廉价杂志集团担任编辑。</p><p>　　1938年，布朗发表了第一篇小说《镍币之月》（The Moon for a Nickel），刊登在《斯崔特和史密斯侦探小说杂志》（Street and Smith's Detective Story Magazine）。从那时开始，布朗成为廉价杂志的固定投稿者，在不同类型的杂志上发表，包括《一角侦探》（Dime Mystery）、《星球故事》（Planet Stories）、《怪异故事》（Weird Tales）。他在廉价小说读者中拥有一大堆拥趸。</p><p>　　布朗第一次普遍的成功是因为发表了第一部长篇小说《传说中的高级夜总会》（The Fabulous Clipjoint，1947），这部作品的主角是一对叔侄组合艾德和阿姆·亨特（Ed and Am Hunter）。他还因此赢得了1984年的埃德加奖。布朗的的经济状况得到改善，他搬到纽约成为一名高级编辑。同时他和第一任妻子海伦（Helen）离婚。</p><p>　　他接下来的侦探小说也非常成功，包括《死亡套环》（The Dead Ringer，1948）、《尖叫的米米》（The Screaming Mimi，1949）。1949年末，他遇到了伊丽莎白·查利尔（Elizabeth Charlier），二人结婚后搬到新墨西哥州的陶斯。廉价小说集团倒闭之后，布朗成为了一名受欢迎的犯罪小说家。随着电视在娱乐业中所占的份量越来越重，布朗也将他的小说改编为电视片。</p><p>　　布朗的身体一直不好，而且他偶尔酗酒对身体健康更是无益。因为呼吸疾病，布朗和妻子在1954年搬到亚利桑那州图森。尽管他为一些报酬很高的杂志写作，诸如《花花公子》（Playboy），但是他已经在走下坡。他的最后一部长篇小说《墨菲太太的内衣裤》（Mrs. Murphy’s Underpants，1963）已经不是水准之作。他还写了一些短篇小说，但是他全职写作的时代已经过去。1972年，布朗因为肺气肿去世。(ellry)</p>]]>
                    </description>
                    <pubDate>Fri, 11 Mar 2022 14:33:56 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2049:统计最高分的节点数目]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2049统计最高分的节点数目</link>
                    <description>
                            <![CDATA[<p>2022年03月11日 力扣每日一题</p><h1 id="题目">题目</h1><p>给你一棵根节点为 <code>0</code> 的 <strong>二叉树</strong> ，它总共有 <code>n</code> 个节点，节点编号为 <code>0</code> 到 <code>n - 1</code> 。同时给你一个下标从 <strong>0</strong> 开始的整数数组 <code>parents</code> 表示这棵树，其中 <code>parents[i]</code> 是节点 <code>i</code> 的父节点。由于节点 <code>0</code> 是根，所以 <code>parents[0] == -1</code> 。</p><p>一个子树的 <strong>大小</strong> 为这个子树内节点的数目。每个节点都有一个与之关联的 <strong>分数</strong> 。求出某个节点分数的方法是，将这个节点和与它相连的边全部 <strong>删除</strong> ，剩余部分是若干个 <strong>非空</strong> 子树，这个节点的 <strong>分数</strong> 为所有这些子树 <strong>大小的乘积</strong> 。</p><p>请你返回有 <strong>最高得分</strong> 节点的 <strong>数目</strong> 。</p><p> </p><p><strong>示例 1:</strong></p><p><img alt="example-1" src="https://assets.leetcode.com/uploads/2021/10/03/example-1.png" style="width: 604px; height: 266px;"></p><pre><b>输入：</b>parents = [-1,2,0,2,0]<b>输出：</b>3<strong>解释：</strong>- 节点 0 的分数为：3 * 1 = 3- 节点 1 的分数为：4 = 4- 节点 2 的分数为：1 * 1 * 2 = 2- 节点 3 的分数为：4 = 4- 节点 4 的分数为：4 = 4最高得分为 4 ，有三个节点得分为 4 （分别是节点 1，3 和 4 ）。</pre><p><strong>示例 2：</strong></p><p><img alt="example-2" src="https://assets.leetcode.com/uploads/2021/10/03/example-2.png" style="width: 95px; height: 143px;"></p><pre><b>输入：</b>parents = [-1,2,0]<b>输出：</b>2<strong>解释：</strong>- 节点 0 的分数为：2 = 2- 节点 1 的分数为：2 = 2- 节点 2 的分数为：1 * 1 = 1最高分数为 2 ，有两个节点分数为 2 （分别为节点 0 和 1 ）。</pre><p> </p><p><strong>提示：</strong></p><ul>    <li><code>n == parents.length</code></li>    <li><code>2 <= n <= 10<sup>5</sup></code></li>    <li><code>parents[0] == -1</code></li>    <li>对于 <code>i != 0</code> ，有 <code>0 <= parents[i] <= n - 1</code></li>    <li><code>parents</code> 表示一棵二叉树。</li></ul><div><div>Related Topics</div><div><li>树</li><li>深度优先搜索</li><li>数组</li><li>二叉树</li></div></div><h1 id="个人解法">个人解法</h1><p>思路：</p><p>　　这题是要返回有 <strong>最高得分</strong>节点的 <strong>数目</strong>，那么就要将每一个节点的分数都算一遍，而每一个节点的分数，是由以下几个数的乘积，包括，该节点下左子树中节点的数目、该节点下右子树中节点的数目，以及总节点数-改节点为跟节点的树的节点数。</p><p>　　那么，我的解题步骤如下：</p><ol><li><p>我先根据题目给的parents数组分别统计每个节点的直连子节点，将其存放进map中。</p></li><li><p>根据map运用递归求出每一个节点做为根节点的子树中的节点数，将其存入counts数组中</p></li><li><p>接下来遍历求每一个节点的分数，并且记入最大得分及节点的数量</p></li></ol><p>下面是java的代码解法：</p><pre><code class="language-java">class Solution {    // 记录每一个节点作为根节点的子树中节点的数量    int[] counts;    public int countHighestScoreNodes(int[] parents) {        int size = parents.length;        // 记录每个节点的直接子节点        Map&lt;Integer, List&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;();        for (int i = 0; i &lt; size; i++) {            map.put(i, new ArrayList&lt;&gt;());        }        for (int i = 1; i &lt; size; i++) {            map.get(parents[i]).add(i);        }        // 记录每个子节点为根节点的树中节点数        counts = new int[size];        for (int i = 0; i &lt; size; i++) {            if (counts[i] &gt; 0) {                continue;            }            counts[i] = dfs(map.get(i), map);        }        // 遍历计算每个节点的得分并统计结果        long mul = 1;        for (int num : map.get(0)) {            mul *= counts[num];        }        int count = 1;        for (int i = 1; i &lt; size; i++) {            long temp = 1;            for (int num : map.get(i)) {                temp *= counts[num];            }            temp *= (size - counts[i]);            if (temp &gt; mul) {                mul = temp;                count = 1;            } else if (temp == mul) {                count++;            }        }        return count;    }    /**     * 计算每个节点为根节点的树中节点数     */    private int dfs(List&lt;Integer&gt; list, Map&lt;Integer, List&lt;Integer&gt;&gt; map) {        if (list.size() == 0) {            return 1;        }        int count = 1;        for (int i : list) {            if (counts[i] &gt; 0) {                count += counts[i];            } else {                count += dfs(map.get(i), map);            }        }        return count;    }}</code></pre>]]>
                    </description>
                    <pubDate>Fri, 11 Mar 2022 12:05:16 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[maven打jar包时本地依赖包未在其中？]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/maven打jar包时本地依赖包未在其中</link>
                    <description>
                            <![CDATA[<p>今天，运行jar包时，报错了，报的内容是不存在某一个依赖包中的类，经过一番排查，发现这个类是下面这种形式依赖的</p><pre><code class="language-xml">&lt;dependency&gt;&lt;groupId&gt;com.oracle&lt;/groupId&gt;&lt;artifactId&gt;ojdbc6&lt;/artifactId&gt;&lt;version&gt;11.2.0.4&lt;/version&gt;&lt;scope&gt;system&lt;/scope&gt;&lt;systemPath&gt;D:/work/ojdbc6-11.2.0.4.jar&lt;/systemPath&gt;&lt;/dependency&gt;</code></pre><p>针对依赖包是在本地的这种情况，需要在pom中添加includeSystemScope=true，参考如下：</p><pre><code class="language-xml">&lt;build&gt;&lt;plugins&gt;&lt;plugin&gt;&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;&lt;version&gt;2.1.7.RELEASE&lt;/version&gt;&lt;configuration&gt;&lt;includeSystemScope&gt;true&lt;/includeSystemScope&gt;&lt;/configuration&gt;&lt;/plugin&gt;&lt;/plugins&gt;&lt;/build&gt;</code></pre>]]>
                    </description>
                    <pubDate>Thu, 10 Mar 2022 14:18:41 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[推理界的3月10号]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/推理界的3月10号</link>
                    <description>
                            <![CDATA[<p>今天是3月10日，在推理界，历史的今天有如下事件：</p><ul><li>古处诚二（日本）诞辰52周年</li></ul><h1 id="古处诚二">古处诚二</h1><p>　　1970年出生于褔冈县、并曾经参与航空自卫队长达六年的古处诚二，2000年以自卫队基地为舞台的推理小说《Unknown》获得第十四回梅菲斯特奖，其后同年再发表以地震灾难为主题的《少年们的密室》、及于翌年(2001)再以自卫队组织为主题创作了《未完成》，接着更以战争为题材发表其他类型的非推理小说。2005年以《七月七日》入选直木奖候选</p>]]>
                    </description>
                    <pubDate>Thu, 10 Mar 2022 10:40:38 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣589:N 叉树的前序遍历]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣589n叉树的前序遍历</link>
                    <description>
                            <![CDATA[<p>2022年03月10日 力扣每日一题</p><h1 id="题目">题目</h1><p>给定一个 n&nbsp;叉树的根节点 <meta charset="UTF-8" />&nbsp;<code>root</code>&nbsp;，返回 <em>其节点值的<strong> 前序遍历</strong></em> 。</p><p>n 叉树 在输入中按层序遍历进行序列化表示，每组子节点由空值 <code>null</code> 分隔（请参见示例）。</p><p><br /><strong>示例 1：</strong></p><p><img src="https://assets.leetcode.com/uploads/2018/10/12/narytreeexample.png" style="height: 193px; width: 300px;" /></p><pre><strong>输入：</strong>root = [1,null,3,2,4,null,5,6]<strong>输出：</strong>[1,3,5,6,2,4]</pre><p><strong>示例 2：</strong></p><p><img alt="" src="https://assets.leetcode.com/uploads/2019/11/08/sample_4_964.png" style="height: 272px; width: 300px;" /></p><pre><strong>输入：</strong>root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]<strong>输出：</strong>[1,2,3,6,7,11,14,4,8,12,5,9,13,10]</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li>节点总数在范围<meta charset="UTF-8" />&nbsp;<code>[0, 10<sup>4</sup>]</code>内</li><li><code>0 &lt;= Node.val &lt;= 10<sup>4</sup></code></li><li>n 叉树的高度小于或等于 <code>1000</code></li></ul><p>&nbsp;</p><p><strong>进阶：</strong>递归法很简单，你可以使用迭代法完成此题吗?</p><div><div>Related Topics</div><div><li>栈</li><li>树</li><li>深度优先搜索</li></div></div><h1 id="个人解法">个人解法</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    List&lt;Integer&gt; list = new ArrayList&lt;&gt;();    public List&lt;Integer&gt; preorder(Node root) {        dfs(root);        return list;    }    void dfs(Node root) {        if (root == null) {            return;        }        list.add(root.val);        for (Node node : root.children) {            dfs(node);        }    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">&quot;&quot;&quot;# Definition for a Node.class Node:    def __init__(self, val=None, children=None):        self.val = val        self.children = children&quot;&quot;&quot;from typing import Listclass Solution:    def preorder(self, root: 'Node') -&gt; List[int]:        result = []        def dfs(node):            if node:                result.append(node.val)                for child in node.children:                    dfs(child)        dfs(root)        return result</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Thu, 10 Mar 2022 09:51:36 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣798:得分最高的最小轮调]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣798得分最高的最小轮调</link>
                    <description>
                            <![CDATA[<p>2022年03月09日 力扣每日一题</p><h1 id="题目">题目</h1><p>给你一个数组 <code>nums</code>，我们可以将它按一个非负整数 <code>k</code> 进行轮调，这样可以使数组变为 <code>[nums[k], nums[k + 1], ... nums[nums.length - 1], nums[0], nums[1], ..., nums[k-1]]</code> 的形式。此后，任何值小于或等于其索引的项都可以记作一分。</p><ul>    <li>例如，数组为 <code>nums = [2,4,1,3,0]</code>，我们按 <code>k = 2</code> 进行轮调后，它将变成 <code>[1,3,0,2,4]</code>。这将记为 <code>3</code> 分，因为 <code>1 > 0</code> [不计分]、<code>3 > 1</code> [不计分]、<code>0 <= 2</code> [计 1 分]、<code>2 <= 3</code> [计 1 分]，<code>4 <= 4</code> [计 1 分]。</li></ul><p>在所有可能的轮调中，返回我们所能得到的最高分数对应的轮调下标 <code>k</code> 。如果有多个答案，返回满足条件的最小的下标 <code>k</code> 。</p><p> </p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>nums = [2,3,1,4,0]<strong>输出：</strong>3<strong>解释：</strong>下面列出了每个 k 的得分：k = 0,  nums = [2,3,1,4,0],    score 2k = 1,  nums = [3,1,4,0,2],    score 3k = 2,  nums = [1,4,0,2,3],    score 3k = 3,  nums = [4,0,2,3,1],    score 4k = 4,  nums = [0,2,3,1,4],    score 3所以我们应当选择 k = 3，得分最高。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>nums = [1,3,0,2,4]<strong>输出：</strong>0<strong>解释：</strong>nums 无论怎么变化总是有 3 分。所以我们将选择最小的 k，即 0。</pre><p> </p><p><strong>提示：</strong></p><ul>    <li><code>1 <= nums.length <= 10<sup>5</sup></code></li>    <li><code>0 <= nums[i] < nums.length</code></li></ul><div><div>Related Topics</div><div><li>数组</li><li>前缀和</li></div></div><h1 id="个人解法">个人解法</h1><p>思路：</p><p><code>arrs[k]</code>代表轮调k次的分数，然后<code>[left,right]</code>区间内的值代表能得分的k值，那么，</p><pre><code class="language-java">left = i + 1right = i - nums[i]</code></pre><p>考虑到超出数组范围的问题，因此，修改为</p><pre><code class="language-java">// size为数组长度left = (i + 1) % size;right = (i - nums[i] + size) % size;</code></pre><p>接下来，我们要考虑<code>[left,right]</code>是否是有效区间</p><ul><li><p>如果是在这个区间内得分+1</p></li><li><p>如果不是有效区间，那么区间将拆分成<code>[0,right]</code>和<code>[left,size-1]</code>两部分，这两部分区间内得分+1</p></li></ul><p>最后我们对数组进行设置，这部分可以使用差分实现</p><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public int bestRotation(int[] nums) {        int size = nums.length;        int[] arrs = new int[size + 1];        for (int i = 0; i &lt; size; i++) {            int left = (i + 1) % size;            int right = (i - nums[i] + size) % size;            if (left &gt; right) {                arrs[0]++;                arrs[size]--;            }            arrs[left]++;            arrs[right + 1]--;        }        for (int i = 1; i &lt; size + 1; i++) {            arrs[i] += arrs[i - 1];        }        int result = 0;        for (int i = 1; i &lt; size + 1; i++) {            if (arrs[i] &gt; arrs[result]) {                result = i;            }        }        return result;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from typing import Listclass Solution:    def bestRotation(self, nums: List[int]) -&gt; int:        size = len(nums)        arrs = [0] * (size + 1)        for i in range(size):            left = (i + 1) % size            right = (i - nums[i] + size) % size            if left &gt; right:                arrs[0] += 1                arrs[size] -= 1            arrs[left] += 1            arrs[right + 1] -= 1        for i in range(1, size + 1):            arrs[i] += arrs[i - 1]        result = 0        for i in range(1, size + 1):            if arrs[i] &gt; arrs[result]:                result = i        return result</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Wed, 09 Mar 2022 16:42:38 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣504:七进制数]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣504七进制数</link>
                    <description>
                            <![CDATA[<p>2022年02月14日 力扣每日一题</p><h1 id="题目">题目</h1><p>给定一个整数 <code>num</code>，将其转化为 <strong>7 进制</strong>，并以字符串形式输出。</p><p>&nbsp;</p><p><strong>示例 1:</strong></p><pre><strong>输入:</strong> num = 100<strong>输出:</strong> "202"</pre><p><strong>示例 2:</strong></p><pre><strong>输入:</strong> num = -7<strong>输出:</strong> "-10"</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>-10<sup>7</sup>&nbsp;&lt;= num &lt;= 10<sup>7</sup></code></li></ul><div><div>Related Topics</div><div><li>数学</li></div></div><h1 id="个人解法">个人解法</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public String convertToBase7(int num) {        boolean bl = num &lt; 0;        num = Math.abs(num);        StringBuilder str = new StringBuilder();        while (num &gt;= 7) {            str.insert(0, num % 7);            num /= 7;        }        str.insert(0, num);        if (bl) {            str.insert(0, '-');        }        return str.toString();    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">class Solution:    def convertToBase7(self, num: int) -&gt; str:        bl = num &lt; 0        s = ''        num = abs(num)        while num &gt;= 7:            s = str(num % 7) + s            num //= 7        s = str(num) + s        if bl:            s = '-' + s        return s</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Mon, 07 Mar 2022 14:15:07 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[推理界的3月7号]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/推理界的3月7号</link>
                    <description>
                            <![CDATA[<p>今天是3月7日，在推理界，历史的今天有如下事件：</p><ul><li><p>仁木悦子（日本）诞辰94周年</p></li><li><p>种村直树（日本）诞辰86周年</p></li><li><p>佐飞通俊（日本）诞辰62周年</p></li><li><p>贾德森·菲利普斯（美国）逝世33周年</p></li></ul><h1 id="仁木悦子">仁木悦子</h1><p>　　日本名女推理小说作家。</p><p>　　仁木悦子的经历尤其令人注目：1928年生于东京，原名大井三重子、她幼年无忧无虑，但四岁那年患结核性胸椎骨疽病，以致下肢瘫痪，半身不遂。七岁那年父亲去世，不久，母亲也亡故。疾病缠身的仁木悦子幸亏有哥哥大井羲光照顾，他每天教她读书。第二次世界大战爆发，16岁的仁木悦子由哥哥背着来到富山乡下居住。她只读到小学三年级，但却看了不少书，并从18岁起开始写作。她先练习写童话，发表在《儿童俱乐部》和《母亲之友》杂志上，她的30多篇童话小说还结集出版。后来她又成了“克里斯蒂小说迷”，并写出推理小说《猫知道》。这部小说的主角是一对兄妹侦探，哥哥雄太郎是植物系大学生，妹妹悦子是音乐系学生，这对兄妹通过一只猫的经历，侦破了一起谋杀案。作品中渗入作者与她哥哥的影子，推理手法十分细腻，许多伏线埋在紧张的情节之中，把粗心的读者引人迷途，在作品中可见女作家的风格。故事的进展采用侦探的助手叙述的方式，叙述者仁木悦子与作者同名的形式在日本就是由仁木悦子创下的成功先例。之后在日本，作者与作品同名的作品不少。</p><p>　　以仁木兄妹为侦探，作者之后继续撰写了《林中之家》、《有刺之树》、《黑色的飘带》等三部长篇和《黄色的花》等若干短篇。</p><p>　　仁木悦子幼时卧病在床，玩伴就是猫，所以她一直喜欢猫，不但让猫在《猫知道》里扮演重要的角色，她所出版的许多推理小说的封面，也都请画家画描，晚年时还主编了一本以“猫”为主题的小说集。她家中的猫则是女佣外出时，从外面捡回来的遭人遗弃的小猫。</p><p>　　《猫知道》写于1957年，参加了江户川乱步侦探小说奖的评选。经过评委投票，《猫知道》在96篇征文作品中名列第一，并获第三届江户川乱步奖。</p><p>　　由于评委都不认识作者，当仁木悦子由她哥哥大井羲光和亲友抬着参加颁奖仪式时，全场引起了轰动。人们意想不到，一个半身不遂、不能走动的女性竟有如此聪颖的智慧与坚韧的毅力，她赢得了热烈的掌声。在闪光灯中，第一次见到仁木悦子的江户川乱步亲自给她发奖、奖品是一人座“福尔摩斯座像”，还有五万日元的奖金。评委木木高太郎则发表了一段讲话：“《呼啸山庄》在英国文学史上占有不朽的地位；女作家艾米莉·勃朗蒂病魔缠身，能写出这样的杰作。仁木悦子君也是有病在身，相信她也能写出与勃朗蒂媲美的好书。”29岁的仁术悦子激动得热泪盈眶，她是20多年来第一次离开家门。事后她回忆道：“我走进豪华的会场大厅，看见闪闪发光的水晶吊灯，以为自己走进了童话王国。”</p><p>　　仁木悦子获奖后，《猫知道》印数剧增。15万册一销而空，后来又拍成电影。丰厚的稿酬收入改善了仁木悦子艰难的处境，她住医院进行了5 次手术，终于能在家中行走，并坐着轮椅车上街观光。一位翻译家同她结了婚，婚后两人和谐美满。仁木悦子不仅成为丈夫的助手，而且又先后写出了7 部长篇推理小说，《林中小屋》（1959 年）、《杀人线路图》（1960年）、《有刺的树》（1961年）、《黑色缎带》（1962年）、《两张底片》（1964年）、《枯叶色的街》（1966年）、《冰冷的街道》（1973年）。有5 部小说仍以兄妹侦探为主角。《两张底片》则是以一对夫妇联手破案。《枯叶色的街》是个贫穷的青年与书店女职员被卷进凶案，成为破案主角。这些推理小说都得到了读者的好评。</p><p>　　1980年的&lt;赤的猫&gt;获得第三十四届日本推理作家协会短篇赏。仁木悦子最后于1986年因肾病逝世，享年58岁。</p><p>　　《猫知道》被日本评论家誉为推理小说史上的“第二次浪潮”。在同一年，松本清张也发表了推理名篇《点与线》。这两部小说一扫日本侦探小说中阴森诡秘的文风，替而代之清新简朴的风格。仁木悦子以女性细腻的文笔，写出了社会推理小说，尽管她身患重疾、但她的小说却给人乐观健康的感受。她注重细节的挖掘，留给读者深刻的印象。继仁木悦子之后，许多推理小说家都自觉地摆脱“变格派”的风格，推重社会推理小说的写实手法。从这一点上说，仁木悦子对日本推理小说的发展有着重要的贡献。</p><h1 id="种村直树">种村直树</h1><p>　　种村直树（1936年3月7日－），日本作家、随笔家、评论家。</p><p>　　1973年开始创作，从事与铁路有关的创作，发表过很多铁路相关的报告文学、时评、游记、推理小说。</p><p>　　出生于滋贺县大津市。滋贺县立大津东高中（现滋贺县立膳所高中）、京都大学法学系毕业。</p><p>　　1972年在每日新闻当记者。在此期间掌握了丰富的铁道知识和创作能力，在当时“铁路杂志”总编辑竹岛纪元的鼓动下，执笔创作了《列车追迹》并开始连载。成为自由撰稿人之后，成为“社会派”推理小说的主要创作作家之一。</p><p>　　代表作《铁道旅行术》、《日本国有铁道最后的事件》、《“青春18车票”之旅》等。</p><h1 id="佐飞通俊">佐飞通俊</h1><p>　　佐飞通俊（1960年3月7日－），日本作家、文艺评论家。</p><p>　　出身于福井县。中央大学文学系哲学科毕业，在新闻社工作。1991年《静音系统》（静かなるシステム）（刊登于“群像”1991年6月号）获第34届群像新人文学奖（评论部门）优秀作品。</p><p>　　2006年开始创作小说，2月出版《孤独通告》（円環の孤独，讲谈社小说），同年8月出版《爱因斯坦游戏》（アインシュタイン·ゲーム，讲谈社小说），2007年4月又推出“宴の果て　死は独裁者に”（讲谈社小说）。</p><h1 id="贾德森菲利普斯">贾德森·菲利普斯</h1><p>　　贾德森·菲利普斯，全名贾德森·潘特寇斯特·菲利普斯（Judson Pentecost Philips，1903年8月10号- 1989年3月7日），美国侦探小说作家，他以休·潘特寇斯特、菲利普·欧文的笔名和他的本名发表了100多部侦探小说，上世纪30年代他还写了为数众多的体育运动类小说。</p><p>　　他出生在美国马萨诸塞州诺斯菲尔德，1925年从哥伦比亚大学毕业。</p><p>　　20世纪的20年代到30年代，菲利普斯开始为“纸浆”杂志撰写短篇小说，他还同时撰写剧本和一家报纸的专栏。1950年，他进入沙龙剧场负责剧本写作和宣传。</p><p>　　1973年，他获得美国侦探作家协会（MWA）颁发的最高荣誉奖项——大师奖。</p><p>　　1989年，菲利普斯因肺气肿引起并发症，在康涅狄格州迦南去世，享年85岁。他留下妻子诺玛·伯顿·菲利普斯、三个儿子（大卫、约翰、丹尼尔）和一个女儿（卡罗琳·诺伍德）。</p>]]>
                    </description>
                    <pubDate>Mon, 07 Mar 2022 09:21:47 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[推理界的3月5号]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/推理界的3月5号</link>
                    <description>
                            <![CDATA[<p>今天是3月5日，在推理界，历史的今天有如下事件：</p><ul><li><p>水谷准（日本）诞辰118周年</p></li><li><p>《广益丛报》（中国）第六十五号刊载署名“冷血（陈景韩）戏作”《歇洛克来游上海第一案》117周年</p></li><li><p>谷克二（日本）诞辰81周年</p></li></ul><h1 id="水谷准">水谷准</h1><p>　　水谷准（1904年3月5日－2001年3月20日），日本小说家、推理作家、翻译家、编辑。</p><p>　　出生于北海道函馆市。旧制函馆中学（现北海道函馆中部高中）中途退学后，进入东京早稻田高中读书。读书期间，1922年以《好敌手》参加“新青年”的有奖征稿第一等入选。早稻田大学文学部法国文学系毕业。1929年接替“新青年”总编辑的职务。1938年一度离职，1939年到1945年再次担任“新青年”的总编辑。</p><p>　　1952年《决斗》（ある決闘）获第5届侦探作家俱乐部奖短篇奖。</p><p>　　二战之后较多创作与高尔夫球有关的作品。</p><h1 id="谷克二">谷克二</h1><p>　　谷克二（1941年3月5日－），日本小说家，被称为“狩猎冒险小说之王（狩猎冒险小说第一人者）”。出生于宫崎县延冈市。本名谷正胜。</p><p>　　1963年毕业于早稻田大学商学系。在德国大众汽车公司工作过，之后去了英国，在伦敦大学主修历史经济学。回国后，开始创作生涯。</p><p>　　1974年，凭借处女作《追うもの》获得第1届野性时代新人奖。1978年又以《狙击者》获得第5届角川小说奖。他的作品《サバンナ》（又译作《西班牙的短暂夏天》）以及《越境线》先后获得直木奖候补作。</p>]]>
                    </description>
                    <pubDate>Sat, 05 Mar 2022 10:45:18 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[推理界的3月4号]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/推理界的3月4号</link>
                    <description>
                            <![CDATA[<p>今天是3月4日，在推理界，历史的今天有如下事件：</p><ul><li><p>程小青（中国）出席纪念白居易诞辰诗会65周年</p></li><li><p>妹尾韶夫（日本）诞辰130周年</p></li><li><p>詹姆斯·艾尔罗伊（美国）诞辰74周年</p></li><li><p>黑川博行（日本）诞辰73周年</p></li><li><p>半村良（日本）逝世20周年</p></li></ul><h1 id="程小青">程小青</h1><p>　　程小青（1893—1976）【原名程青心，又名程辉斋】</p><p>　　籍贯：江苏吴县人。</p><p>　　身平介绍：少年家贫，曾在钟表店当学徒，自学外语和热爱看书，他18岁时开始从事文学写作，先是与周瘦鹃合作翻译柯南·道尔作品，后来创作《霍桑探案》，一举成名。</p><p>　　据史料介绍，程小青在21岁时发表的《灯光人影》，被《新闻报》举行的征文大赛选中，他小说中的侦探原名霍森，因排字工人误排，于是便成了霍桑。《霍桑探案》发表之后，程小青不断收到读者大量来信。是读者的鼓励，促使程小青先后写出了《江南燕》、《珠项圈》、《黄浦江中》、《八十四》、《轮下血》、《裹棉刀》、《恐怖的话剧》、《雨夜枪声》、《白衣怪》、《催命符》、《索命钱》、《新婚劫》、《活尸》、《逃犯》、《血手印》、《黑地牢》、《无头案》等30余部侦探小说。著名报人郑逸梅曾称赞他：“毕生精力，尽瘁于此，也就成为侦探小说的巨擘。”</p><p>　　程小青的创作，据另一位著名报人范烟桥称“模仿了柯南道尔的写法”，但他又塑造了“中国的福尔摩斯”。为了达到这一目的，程小青作为函授生，受业于美国大学函授科，进修犯罪心理学与侦探学的学习，他从理论上学习西欧侦探理论，在实践中又把中国旧社会发生的案例加以改造。他在谈到创作时，多次谈到自己如何设计侦探小说的名字，怎样取材与裁剪，怎样构思开头与结尾，他把美国作家韦尔斯的专著《侦探小说技艺论》和美国心理学家聂克逊博士的专著《著作人应知的心理学》作为教科书。在小说中，程小青设计了霍桑与包朗一对搭档，类似福尔摩斯与华生医生，但在案件的取材上，程小青着重描写旧中国社会弊病引发的凶杀案，注重人物的心理分析，把凶杀与现实生活的投影结合起来，因此形成了自己的特点与风格。</p><h1 id="妹尾韶夫">妹尾韶夫</h1><p>　　妹尾韶夫（1892年3月4日－1962年4月19日），日本翻译家、侦探小说作家。出生于冈山县津山市。</p><p>　　早稻田大学英文系毕业后，1922年为“新青年”等杂志翻译英美侦探小说，其中多数是阿加莎·克里斯蒂的作品。1925年以后以妹尾安艺夫名义创作，发表了30到40个短篇小说。</p><p>　　在“新青年”担当每月评论的胡铁梅、“宝石”杂志每月评论者小原俊一，据说都是妹尾的笔名。</p><p>　　1962年因脑溢血去世，终年70岁。</p><h1 id="詹姆斯艾尔罗伊">詹姆斯·艾尔罗伊</h1><p>　　詹姆斯·艾尔罗伊 （詹姆斯·艾尔罗瓦） James Ellroy（1948年3月4日美国加州洛杉矶-）</p><p>　　类型：硬汉；警察程序；私人侦探；倒叙</p><p>　　主要系列：</p><ul><li>“洛依·霍普金斯”三部曲Lloyd Hopkins trilogy, 1984-1986</li><li>洛杉矶四部曲L.A. quartet, 1987-1992</li><li>American Underworld trilogy/Underworld USA trilogy, 1995-</li></ul><p>　　艾尔罗伊本名李·厄尔·艾尔罗伊（Lee Earle Ellroy）。父亲阿曼德·艾尔罗伊（Armand Ellroy）是反犹太主义者，副业是会计师，母亲杰尼瓦·奥德丽·“简”·希利克·艾尔罗伊（Geneva Odelia “Jean” Hilliker Ellroy）是注册护士。艾尔罗伊的父母于1940年结婚，1954年离婚。艾尔罗伊被判给母亲，接着搬到了埃尔蒙特市。据艾尔罗伊回忆，母亲经常在周六晚上酗酒。1958年6月22日发生了一件对于艾尔罗伊一生影响深远的事情，那天他的母亲被人谋杀。之后，艾尔罗伊和父亲一起居住。十一岁生日，父亲送给他一本洛杉矶警察局历史的书籍，他仔细阅读了这本书，立志将来当一名作家。艾尔罗伊是一个有强迫症的读者，他常常去图书馆借书，还从书店里偷犯罪小说。</p><p>　　艾尔罗伊进入犹太费尔法克斯高中（Jewish Fairfax High School），1965年校方知道他父亲的纳粹观点之后将他开除。接着他进入美国陆军，但是很快认识到自己不是当兵的材料。他假装口吃，于是很快退伍。他回家之后不久父亲去世了。</p><p>　　那段时间，艾尔罗伊就住在街上，靠着入店行窃和入室盗窃为生。他喝酒，有时候还嗑药，占据着无人的房子。1965年到1977年间，艾尔罗伊因为醉酒、偷窃和非法入室而多次被捕。最后被判入狱八个月。刑满释放之后，他做过一些低等的工作，诸如散发传单，递送邮件，色情书店出纳等等。他继续喝酒，滥用鼻用吸入器。因为患上肺炎和妄想症，艾尔罗伊被送去治疗，1975年治愈。接着他找了一些稳定的工作，比如高尔夫和乡村俱乐部的球童，参加戒酒互助协会（Alcoholics Anonymous）之后他开始创作小说。</p><p>　　艾尔罗伊的第一部长篇小说《布朗的安魂曲》（Brown's Requiem）是一部半自传性质的犯罪小说，风格类似雷蒙德·钱德勒，小说的主人公弗里兹·布朗（Fritz Brown）曾经是一名警官，他戒酒之后变成了一名私人侦探。第二部《秘密行事》（Clandestine，1982）讲述了一名前警官追踪杀害以前爱人的凶手的故事，获得埃德加奖提名。</p><p>　　此后，艾尔罗伊的创作速度保持稳健。他先是发表了“洛依·霍普金斯”三部曲，包括《染血之夜》（Blood on the Moon，1984）、《因起此夜》（Because the Night，1984）、《自殺坡》（Suicide Hill，1986）。1984年他辞去球童，全职写作。他又发表了“洛杉矶四部曲”，包括《黑色大丽花》（The Black Dahlia，1987）、《无处藏身》（The Big Nowhere，1988）、《洛杉矶的秘密》（L.A. Confidential，1990）、《白色爵士舞》（White Jazz，1992）。因此在国内和国际上获得了声誉。《无处藏身》或的1990年侦探小说奖（Prix Mystere Award）。《洛杉矶的秘密》被改编成电影，获得奥斯卡奖提名。2006年《黑色大丽花》被搬上大银幕。</p><p>　　1993年到2004年间，艾尔罗伊在《GQ》杂志上发表了小说和非小说。这些作品结集为《好莱坞夜曲》（Hollywood Nocturnes，1994）、《犯罪之波：来自洛杉矶地下社会的报道和小说》（Crime Wave: Reportage and Fiction from the Underside of L.A.，1999）和《危险的步调》（Breakneck Pace，2000）。</p><p>　　艾尔罗伊结过两次婚，第一任妻子是玛丽·多赫尔蒂（Mary Doherty），第二任是海伦·诺德（Helen Knode），均离婚。2005年他从康涅狄格州纽卡纳安搬到洛杉矶。（ellry）</p><h1 id="黑川博行">黑川博行</h1><p>　　黑川博行（1949年3月4日－），日本小说家。出生于爱媛县。京都市立艺术大学美术学系雕刻科毕业。妻子是日本画家黑川雅子。</p><p>　　毕业后在高中担任美术教师，1984年以《第二次告别》（二度のお别れ）获三得利推理大奖佳作。1986年《猫眼宝石》（キャッツアイころがった）获第4届三得利推理大奖。1996年《伯爵计划》（カウント·プラン）获第49届日本推理作家协会奖（短篇部门）。</p><p>　　获得直木奖候补的作品有《伯爵计划》（カウント·プラン）、《疫病神》、《文福茶釜》、《国境》、《恶果》等。</p><p>　　他还是由船越荣一郎主演的电视连续剧《刑事吉永诚一·泪的事件簿》的原作。</p><h1 id="半村良">半村良</h1><p>　　半村良（1933年10月27日－2002年3月4日），日本小说家。本名清野平太郎。出生于东京府（现东京都），在东京都立两国高中毕业后，先后做过酒吧侍者等多种职业，在广告公司任职期间与广播公司等建立了密切的关系，后开始这方面的工作。</p><p>　　1962年短篇小说《收获》（収穫）获得第2届早川科幻小说大赛（ハヤカワ·SFコンテスト）第三名，正式成为作家，上世纪六十年代在《SF杂志》（SFマガジン）上发表若干个短篇小说后，突然不再发表作品，据说和当时《SF杂志》总编辑福岛正实关系不佳，后转入自由创作。</p><p>　　1971年出版《石之血脉》（石の血脈）开创了浪漫传奇小说流派，这种风格对后世很多作家产生了影响。</p><p>　　1975年《雨やどり》获得直木奖。</p>]]>
                    </description>
                    <pubDate>Fri, 04 Mar 2022 11:28:18 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[python3学习笔记--集合、元组、字典、列表对比]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/python3学习笔记--集合元组字典列表对比</link>
                    <description>
                            <![CDATA[<h1 id="数据结构">数据结构</h1><p>Python支持以下数据结构：列表，字典，元组，集合。</p><p><strong>何时使用字典：</strong></p><ul><li><p>当您需要键：值对之间的逻辑关联时。</p></li><li><p>当您需要基于自定义密钥快速查找数据时。</p></li><li><p>当你的数据不断修改时。请记住，字典是可变的。</p></li></ul><p><strong>何时使用其他类型：</strong></p><ul><li><p>如果您有一些不需要随机访问的数据集合，请使用列表。当你需要一个简单的，可迭代的频繁修改的集合可以使用列表。</p></li><li><p>如果你需要元素的唯一性，使用集合。</p></li><li><p>当数据无法更改时使用元组。</p></li></ul><blockquote><p>很多时候，元组与字典结合使用，例如元组可能代表一个关键字，因为它是不可变的。</p></blockquote><h1 id="1列表">1、列表</h1><p>使用<strong>方括号</strong>创建</p><pre><code class="language-python">words = [&quot;Hello&quot;, &quot;world&quot;, &quot;!&quot;]</code></pre><blockquote><p>使用空的方括号创建空列表</p><p>可以通过索引来访问</p><p>大多数情况下，列表中的最后一项不会带逗号。然而，在最后一项放置一个逗号是完全有效的，在某些情况下是鼓励的。</p><p>列表的索引是从0开始的，而不是从1开始的</p></blockquote><h1 id="2集合">2、集合</h1><p>使用<strong>花括号</strong>或 <strong>set</strong> 函数创建</p><pre><code class="language-python">num_set = {1, 2, 3, 4, 5}word_set = set([&quot;spam&quot;, &quot;eggs&quot;, &quot;sausage&quot;])</code></pre><blockquote><p>要创建一个空集，必须使用 set()，如 {} 是创建一个空字典。</p><p>集合是<strong>无序</strong>的，这意味着他们不能被索引。</p><p>集合不能包含重复的元素。</p><p>由于存储的方式，检查一个项目是否是一个集合的一部分比检查是不是列表的一部分<strong>更快</strong>。</p><p>集合使用 <strong>add</strong> 添加元素 。</p><p><strong>remove</strong> 方法从集合中删除特定的元素; <strong>pop</strong> 删除随机的元素。</p></blockquote><h1 id="3元组">3、元组</h1><p>元组 使用<strong>圆括号</strong>创建 ，也可以在没有<strong>圆括号</strong>的情况下创建</p><pre><code class="language-python">words = (&quot;spam&quot;, &quot;eggs&quot;, &quot;sausages&quot;,)my_tuple = &quot;one&quot;, &quot;two&quot;, &quot;three&quot;</code></pre><blockquote><p>使用空括号对创建空元组。</p><p>元组比列表快，但是元组不能改变。</p><p>可以使用索引访问元组中的值。</p></blockquote><h1 id="4字典">4、字典</h1><p>字典是用于将任意键映射到值的数据结构</p><pre><code class="language-python">ages = {&quot;Dave&quot;: 24, &quot;Mary&quot;: 42, &quot;John&quot;: 58}</code></pre><blockquote><p>空字典被定义为{}。</p><p>字典 中的每个元素都由一个 键:值 对来表示。</p><p>使用 <strong>字典[&quot;键名&quot;]</strong> 可以获取对应的值。</p></blockquote>]]>
                    </description>
                    <pubDate>Thu, 03 Mar 2022 21:55:20 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[python3学习笔记--列表切片]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/python3学习笔记--列表切片</link>
                    <description>
                            <![CDATA[<p>列表切片（<strong>List slices</strong>）提供了从列表中检索值的更高级的方法。</p><blockquote><p>列表名[num1 : num2 : num3]</p><p>从索引num1到num2（不包括num2）间隔为num3的元素</p><p>num1或num2为负值代表从末尾开始算起的</p><p>num3为负值代表切片进行逆序截取</p></blockquote><p>以下为具体说明</p><h1 id="基本用法">基本用法</h1><p>用两个以冒号分隔的整数索引列表。</p><p>列表切片返回一个包含索引之间旧列表中所有值的新列表。</p><p>例如：</p><pre><code class="language-python">squares = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]print(squares[2:6])print(squares[3:8])print(squares[0:1])</code></pre><p>结果：</p><pre><code class="language-python">[4, 9, 16, 25][9, 16, 25, 36, 49][0]</code></pre><blockquote><p>和Range参数一样，在一个 slice 中提供的第一个索引被包含在结果中，但是第二个索引没有。</p></blockquote><h1 id="省略一个数字">省略一个数字</h1><p>如果省略了切片中的第一个数字，则将从列表第一个元素开始。</p><p>如果第二个数字被省略，则认为是到列表结束。</p><p>例如：</p><pre><code class="language-python">squares = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]print(squares[:7])print(squares[7:])</code></pre><p>结果：</p><pre><code class="language-python">[0, 1, 4, 9, 16, 25, 36][49, 64, 81]</code></pre><blockquote><p>切片也可以用在元组上</p></blockquote><h1 id="带间隔的切片">带间隔的切片</h1><p>列表切片还可以有第三个数字，表示间隔。</p><p>例如：</p><pre><code class="language-python">squares = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]print(squares[::2])print(squares[2:8:3])</code></pre><p>结果：</p><pre><code class="language-python">[0, 4, 16, 36, 64][4, 25]</code></pre><blockquote><p>[2：8：3] 包含从索引2到8间隔3的元素。</p></blockquote><h1 id="带负值">带负值</h1><p>负值也可用于列表切片（和正常列表索引）。当切片（或普通索引）中的第一个和第二个值使用负值时，它们将从列表的末尾算起。</p><p>例如：</p><pre><code class="language-python">squares = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]print(squares[1:-1])print(squares[-3:-1])print(squares[::-1])</code></pre><p>结果：</p><pre><code class="language-python">[1, 4, 9, 16, 25, 36, 49, 64][49, 64][81, 64, 49, 36, 25, 16, 9, 4, 1, 0]</code></pre><blockquote><p>如果切片第三个数值使用负值，则切片进行逆序截取。<br />使用[::-1]作为切片是反转列表的常用方法。</p></blockquote>]]>
                    </description>
                    <pubDate>Tue, 01 Mar 2022 09:47:34 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[python3学习笔记--常用的函数]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/python3学习笔记--常用的函数</link>
                    <description>
                            <![CDATA[<p>{% note info no-icon %}<br />本篇博客内容为学习整理笔记，学习地址为：<br /><a href="https://www.w3cschool.cn/minicourse/play/python3course?cp=427&amp;gid=0">https://www.w3cschool.cn/minicourse/play/python3course?cp=427&amp;gid=0</a><br />{% endnote %}</p><h1 id="字符串函数">字符串函数</h1><h2 id="1join">1、join</h2><p>以另一个字符串作为分隔符连接字符串列表。</p><p>例如：</p><pre><code class="language-python">print(&quot;, &quot;.join([&quot;spam&quot;, &quot;eggs&quot;, &quot;ham&quot;]))# 打印 &quot;spam, eggs, ham&quot;</code></pre><h2 id="2replace">2、replace</h2><p>用另一个替换字符串中的一个子字符串。</p><p>例如：</p><pre><code class="language-python">print(&quot;Hello ME&quot;.replace(&quot;ME&quot;, &quot;world&quot;))# 打印 &quot;Hello world&quot;</code></pre><h2 id="3startswith">3、startswith</h2><p>确定是否在字符串的开始处有一个子字符串。</p><p>例如：</p><pre><code class="language-python">print(&quot;This is a sentence.&quot;.startswith(&quot;This&quot;))# 打印 &quot;True&quot;</code></pre><h2 id="4endswith">4、endswith</h2><p>确定是否在字符串的结尾处有一个子字符串。</p><p>例如：</p><pre><code class="language-python">print(&quot;This is a sentence.&quot;.endswith(&quot;sentence.&quot;))# 打印 &quot;True&quot;</code></pre><h2 id="5lower">5、lower</h2><p>将字符串全部转为小写。</p><p>例如：</p><pre><code class="language-python">print(&quot;AN ALL CAPS SENTENCE&quot;.lower())# 打印  &quot;an all caps sentence&quot;</code></pre><h2 id="6upper">6、upper</h2><p>将字符串全部转为大写。</p><p>例如：</p><pre><code class="language-python">print(&quot;This is a sentence.&quot;.upper())# 打印 &quot;THIS IS A SENTENCE.&quot;</code></pre><h2 id="7split">7、split</h2><p>把一个字符串转换成一个列表。</p><p>例如：</p><pre><code class="language-python">print(&quot;spam, eggs, ham&quot;.split(&quot;, &quot;))# 打印  &quot;['spam', 'eggs', 'ham']&quot;</code></pre><h1 id="数字函数">数字函数</h1><h2 id="1max">1、max</h2><p>查找某些数字或列表的最大值。</p><p>例如：</p><pre><code class="language-python">print(max([1, 2, 9, 2, 4, 7, 8]))# 打印 9</code></pre><h2 id="2min">2、min</h2><p>查找某些数字或列表的最小值。</p><p>例如：</p><pre><code class="language-python">print(min(1, 6, 3, 4, 0, 7, 1))# 打印 0</code></pre><h2 id="3abs">3、abs</h2><p>将数字转成绝对值（该数字与0的距离）。</p><p>例如：</p><pre><code class="language-python">print(abs(-93))print(abs(22))# 打印 93</code></pre><h2 id="4round">4、round</h2><p>要将数字四舍五入到一定的小数位数。</p><h2 id="5sum">5、sum</h2><p>计算一个列表数字的总和。</p><p>例如：</p><pre><code class="language-python">print(sum([1, 2, 3, 4, 5, 6]))# 打印 21</code></pre><h1 id="列表函数">列表函数</h1><h2 id="1all">1、all</h2><p>列表中所有值均为 True 时，结果为 True，否则结果为 False。</p><p>例如：</p><pre><code class="language-python">nums = [55, 44, 33, 22, 11]if all([i &gt; 5 for i in nums]):    print(&quot;All larger than 5&quot;)# 打印 All larger than 5</code></pre><h2 id="2any">2、any</h2><p>列表中只要有一个为 True，结果为 True，反之结果为 False。</p><p>例如：</p><pre><code class="language-python">nums = [55, 44, 33, 22, 11]if any([i % 2 == 0 for i in nums]):   print(&quot;At least one is even&quot;)# 打印 At least one is even5</code></pre><h2 id="3enumerate">3、enumerate</h2><p>用来同时迭代列表的键和值。</p><p>例如：</p><pre><code class="language-python">nums = [55, 44, 33, 22, 11]for v in enumerate(nums):   print(v)# 打印# (0, 55)# (1, 44)# (2, 33)# (3, 22)# (4, 11)</code></pre>]]>
                    </description>
                    <pubDate>Tue, 01 Mar 2022 09:02:19 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣540:有序数组中的单一元素]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣540有序数组中的单一元素</link>
                    <description>
                            <![CDATA[<p>2022年02月14日 力扣每日一题</p><h1 id="题目">题目</h1><p>给定一个只包含整数的有序数组，每个元素都会出现两次，唯有一个数只会出现一次，找出这个数。</p><p> </p><p><strong>示例 1:</strong></p><pre><strong>输入:</strong> nums = [1,1,2,3,3,4,4,8,8]<strong>输出:</strong> 2</pre><p><strong>示例 2:</strong></p><pre><strong>输入:</strong> nums =  [3,3,7,7,10,11,11]<strong>输出:</strong> 10</pre><p> </p><p><meta charset="UTF-8" /></p><p><strong>提示:</strong></p><ul><li><code>1 <= nums.length <= 10<sup>5</sup></code></li><li><code>0 <= nums[i] <= 10<sup>5</sup></code></li></ul><p> </p><p><strong>进阶:</strong> 采用的方案可以在 <code>O(log n)</code> 时间复杂度和 <code>O(1)</code> 空间复杂度中运行吗？</p><div><div>Related Topics</div><div><li>数组</li><li>二分查找</li></div></div><h1 id="个人解法">个人解法</h1><p>根据异或的规则，相同为0，不同为1，这样把所有数都异或一遍，结果就是唯一的只出现一次的数</p><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">public int singleNonDuplicate(int[] nums) {    int result = nums[0];    for (int i = 1; i &lt; nums.length; i++) {        result ^= nums[i];    }    return result;}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">import operatorfrom functools import reducefrom typing import Listclass Solution:    def singleNonDuplicate(self, nums: List[int]) -&gt; int:        return reduce(operator.xor, nums)</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Mon, 14 Feb 2022 09:49:24 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣1189:“气球” 的最大数量]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣1189气球的最大数量</link>
                    <description>
                            <![CDATA[<p>2022年02月13日 力扣每日一题</p><h1 id="题目">题目</h1><p>给你一个字符串&nbsp;<code>text</code>，你需要使用 <code>text</code> 中的字母来拼凑尽可能多的单词&nbsp;<strong>&quot;balloon&quot;（气球）</strong>。</p><p>字符串&nbsp;<code>text</code> 中的每个字母最多只能被使用一次。请你返回最多可以拼凑出多少个单词&nbsp;<strong>&quot;balloon&quot;</strong>。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><p><strong><img alt="" src="https://img.huangge1199.cn/blog/day20220213/1536_ex1_upd.jpeg" style="height: 35px; width: 154px;"></strong></p><pre><strong>输入：</strong>text = &quot;nlaebolko&quot;<strong>输出：</strong>1</pre><p><strong>示例 2：</strong></p><p><strong><img alt="" src="https://img.huangge1199.cn/blog/day20220213/1536_ex2_upd.jpeg" style="height: 35px; width: 233px;"></strong></p><pre><strong>输入：</strong>text = &quot;loonbalxballpoon&quot;<strong>输出：</strong>2</pre><p><strong>示例 3：</strong></p><pre><strong>输入：</strong>text = &quot;leetcode&quot;<strong>输出：</strong>0</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= text.length &lt;= 10^4</code></li><li><code>text</code>&nbsp;全部由小写英文字母组成</li></ul><div><div>Related Topics</div><div><li>哈希表</li><li>字符串</li><li>计数</li></div></div><h1 id="个人解法">个人解法</h1><p>一个单词”balloon”分别需要一个'b'、'a'、'n'，以及二个'l'、'o'<br />首先我们统计给的单词中每个字母的个数<br />然后统计'b'、'a'、'n'数量以及'l'、'o'除以2的最小值</p><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public int maxNumberOfBalloons(String text) {        int[] arrs = new int[26];        for (char ch : text.toCharArray()) {            arrs[ch - 'a']++;        }        int count = Math.min(arrs[0], arrs[1]);        count = Math.min(count, arrs['l' - 'a'] / 2);        count = Math.min(count, arrs['o' - 'a'] / 2);        count = Math.min(count, arrs['n' - 'a']);        return count;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">class Solution:    def maxNumberOfBalloons(self, text: str) -&gt; int:        return min(cnts[ch] // 2 if ch in &quot;lo&quot; else cnts[ch] for ch in &quot;balon&quot;) if (cnts := Counter(text)) else 0</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Sun, 13 Feb 2022 22:32:48 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣1020:飞地的数量]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣1020飞地的数量</link>
                    <description>
                            <![CDATA[<p>2022年02月12日 力扣每日一题</p><h1 id="题目">题目</h1><p>给你一个大小为 <code>m x n</code> 的二进制矩阵 <code>grid</code> ，其中 <code>0</code> 表示一个海洋单元格、<code>1</code> 表示一个陆地单元格。</p><p>一次 <strong>移动</strong> 是指从一个陆地单元格走到另一个相邻（<strong>上、下、左、右</strong>）的陆地单元格或跨过 <code>grid</code> 的边界。</p><p>返回网格中<strong> 无法 </strong>在任意次数的移动中离开网格边界的陆地单元格的数量。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><img alt="" src="https://assets.leetcode.com/uploads/2021/02/18/enclaves1.jpg" style="height: 200px; width: 200px;" /><pre><strong>输入：</strong>grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]<strong>输出：</strong>3<strong>解释：</strong>有三个 1 被 0 包围。一个 1 没有被包围，因为它在边界上。</pre><p><strong>示例 2：</strong></p><img alt="" src="https://assets.leetcode.com/uploads/2021/02/18/enclaves2.jpg" style="height: 200px; width: 200px;" /><pre><strong>输入：</strong>grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]<strong>输出：</strong>0<strong>解释：</strong>所有 1 都在边界上或可以到达边界。</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>m == grid.length</code></li><li><code>n == grid[i].length</code></li><li><code>1 &lt;= m, n &lt;= 500</code></li><li><code>grid[i][j]</code> 的值为 <code>0</code> 或 <code>1</code></li></ul><div><div>Related Topics</div><div><li>深度优先搜索</li><li>广度优先搜索</li><li>并查集</li><li>数组</li><li>矩阵</li></div></div><h1 id="个人解法">个人解法</h1><p>解题方法：广度优先算法</p><p>这道题是统计无法力扣网络边界的陆地单元格数量，我的思路是反过来统计，用<code>总陆地数量</code>-<code>能离开的陆地数量</code></p><p>这样的话，我就可以用广度优先算法来进行解决，步骤如下：</p><ol><li>将边界的单元格坐标加入到队列，并计数</li><li>依次从队列中取出</li><li>将取出陆地的相邻陆地加入到队列中，并计数</li><li>当队列为空时，遍历数组获取总陆地数，并减去能离开的陆地数量</li></ol><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">import java.util.LinkedList;import java.util.Queue;class Solution {    public int numEnclaves(int[][] grid) {        boolean[][] use = new boolean[grid.length][grid[0].length];        Queue&lt;int[]&gt; queue = new LinkedList&lt;&gt;();        int xl = grid.length;        int yl = grid[0].length;        int count = 0;        for (int i = 0; i &lt; xl; i++) {            if (grid[i][0] == 1) {                queue.add(new int[]{i, 0});                use[i][0] = true;                count++;            }            if (grid[i][yl - 1] == 1 &amp;&amp; !use[i][yl - 1]) {                queue.add(new int[]{i, yl - 1});                use[i][yl - 1] = true;                count++;            }        }        for (int i = 1; i &lt; yl - 1; i++) {            if (grid[0][i] == 1 &amp;&amp; !use[0][i]) {                queue.add(new int[]{0, i});                use[0][i] = true;                count++;            }            if (grid[xl - 1][i] == 1 &amp;&amp; !use[xl - 1][i]) {                queue.add(new int[]{xl - 1, i});                use[xl - 1][i] = true;                count++;            }        }        int[] xp = new int[]{1, -1, 0, 0};        int[] yp = new int[]{0, 0, 1, -1};        while (!queue.isEmpty()) {            int[] arr = queue.poll();            int x = arr[0];            int y = arr[1];            for (int k = 0; k &lt; 4; k++) {                int nx = x + xp[k];                int ny = y + yp[k];                if (nx &gt;= 0 &amp;&amp; nx &lt; grid.length &amp;&amp; ny &gt;= 0 &amp;&amp; ny &lt; grid[0].length &amp;&amp; grid[nx][ny] == 1 &amp;&amp; !use[nx][ny]) {                    queue.add(new int[]{nx, ny});                    use[nx][ny] = true;                    count++;                }            }        }        int sum = 0;        for (int[] ints : grid) {            for (int j = 0; j &lt; yl; j++) {                if (ints[j] == 1) {                    sum++;                }            }        }        return sum - count;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from collections import dequefrom typing import Listclass Solution:    def numEnclaves(self, grid: List[List[int]]) -&gt; int:        use = [[False] * len(grid[0]) for _ in range(len(grid))]        queue = deque()        xl = len(grid)        yl = len(grid[0])        count = 0        for i in range(xl):            if grid[i][0] == 1:                queue.append((i, 0))                use[i][0] = True                count += 1            if grid[i][yl - 1] == 1 and not use[i][yl - 1]:                queue.append((i, yl - 1))                use[i][yl - 1] = True                count += 1        for i in range(1, yl - 1):            if grid[0][i] == 1 and not use[0][i]:                queue.append((0, i))                use[0][i] = True                count += 1            if grid[xl - 1][i] == 1 and not use[xl - 1][i]:                queue.append((xl - 1, i))                use[xl - 1][i] = True                count += 1        while queue:            x, y = queue.pop()            for nx, ny in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):                if nx &lt; 0 or nx &gt;= len(grid) or ny &lt; 0 or ny &gt;= len(grid[0]) or grid[nx][ny] == 0 or use[nx][ny]:                    continue                queue.append((nx, ny))                use[nx][ny] = True                count += 1        sc = sum([sum(row) for row in grid])        return sc - count</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Sat, 12 Feb 2022 22:22:26 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[代码提交到多个git仓库]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/代码提交到多个git仓库</link>
                    <description>
                            <![CDATA[<p>现在我们都习惯于把自己的代码放到远程仓库中，毫无疑问GitHub是首选，但由于国内的网络等各种原因，会导致我们连接不上，这时候我们会考虑放到自建的代码管理仓库或者是gitee上面。</p><p>我们还不想放弃GitHub，那么我们就要考虑将代码提交到多个仓库中。</p><p>比如，我分别在GitHub和gitee上都有格子的仓库：</p><ul><li><a href="https://github.com/huangge1199/my-blog.git">https://github.com/huangge1199/my-blog.git</a></li><li><a href="https://gitee.com/huangge1199_admin/my-blog.git">https://gitee.com/huangge1199_admin/my-blog.git</a></li></ul><p>那么，我可以通过以下命令来进行添加仓库：</p><p>先添加第一个GitHub的仓库地址：</p><pre><code>git remote add origin https://github.com/huangge1199/my-blog.git</code></pre><p>再添加gitee的仓库地址</p><pre><code>git remote set-url --add origin https://gitee.com/huangge1199_admin/my-blog.git</code></pre><p>这样的话我们push时，就会将代码同时推送到两个仓库了。</p><p>当然不想用命令的形式操作，也可以直接修改项目目录下隐藏目录.git中的config文件，在[remote &quot;origin&quot;]中添加多个仓库地址就可以了，参考如下：</p><pre><code>[remote &quot;origin&quot;]url = https://gitee.com/huangge1199_admin/my-blog.gitfetch = +refs/heads/*:refs/remotes/origin/*url = https://github.com/huangge1199/my-blog.git</code></pre>]]>
                    </description>
                    <pubDate>Fri, 11 Feb 2022 17:34:25 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣1984:学生分数的最小差值]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣1984学生分数的最小差值</link>
                    <description>
                            <![CDATA[<p>2022年02月11日 力扣每日一题</p><h1 id="题目">题目</h1><p>给你一个 <strong>下标从 0 开始</strong> 的整数数组 <code>nums</code> ，其中 <code>nums[i]</code> 表示第 <code>i</code> 名学生的分数。另给你一个整数 <code>k</code> 。</p><p>从数组中选出任意 <code>k</code> 名学生的分数，使这 <code>k</code> 个分数间 <strong>最高分</strong> 和 <strong>最低分</strong> 的 <strong>差值</strong> 达到<strong> 最小化</strong> 。</p><p>返回可能的 <strong>最小差值</strong> 。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>nums = [90], k = 1<strong>输出：</strong>0<strong>解释：</strong>选出 1 名学生的分数，仅有 1 种方法：- [<em><strong>90</strong></em>] 最高分和最低分之间的差值是 90 - 90 = 0可能的最小差值是 0</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>nums = [9,4,1,7], k = 2<strong>输出：</strong>2<strong>解释：</strong>选出 2 名学生的分数，有 6 种方法：- [<em><strong>9</strong></em>,<em><strong>4</strong></em>,1,7] 最高分和最低分之间的差值是 9 - 4 = 5- [<em><strong>9</strong></em>,4,<em><strong>1</strong></em>,7] 最高分和最低分之间的差值是 9 - 1 = 8- [<em><strong>9</strong></em>,4,1,<em><strong>7</strong></em>] 最高分和最低分之间的差值是 9 - 7 = 2- [9,<em><strong>4</strong></em>,<em><strong>1</strong></em>,7] 最高分和最低分之间的差值是 4 - 1 = 3- [9,<em><strong>4</strong></em>,1,<em><strong>7</strong></em>] 最高分和最低分之间的差值是 7 - 4 = 3- [9,4,<em><strong>1</strong></em>,<em><strong>7</strong></em>] 最高分和最低分之间的差值是 7 - 1 = 6可能的最小差值是 2</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= k &lt;= nums.length &lt;= 1000</code></li><li><code>0 &lt;= nums[i] &lt;= 10<sup>5</sup></code></li></ul><div><div>Related Topics</div><div><li>数组</li><li>排序</li><li>滑动窗口</li></div></div><h1 id="个人解法">个人解法</h1><p>排序后，使用滑动窗口</p><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public int minimumDifference(int[] nums, int k) {        Arrays.sort(nums);        int min = Integer.MAX_VALUE;        for (int i = 0; i &lt;= nums.length - k; i++) {            min = Math.min(min, nums[i + k - 1] - nums[i]);        }        return min;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from typing import Listclass Solution:    def minimumDifference(self, nums: List[int], k: int) -&gt; int:        if k &gt; 1:            num = sorted(nums)            return min(num[i + k - 1] - num[i] for i in range(len(num) - k + 1))        else:            return 0</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Fri, 11 Feb 2022 13:35:01 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[个人网站加入到搜索引擎中]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/个人网站加入到搜索引擎中</link>
                    <description>
                            <![CDATA[<p>一般来说，搜索引擎中是不会收入你个人网站的，你可以试试用谷歌或者百度等其他搜索引擎看看，能不能收到你个人网站的相关页面？<br />如果搜索不到，你可以申请加入搜索引擎，这个是免费的，下面提供一些搜索引擎的提交地址：</p><ul><li>谷歌博客搜索收录入口:<br /><a href="http://www.google.com/addurl/">http://www.google.com/addurl/</a></li><li>百度收录入口:<br /><a href="http://www.baidu.com/search/url_submit.html">http://www.baidu.com/search/url_submit.html</a></li><li>必应Bing收录入口:<br /><a href="https://www.bing.com/toolbox/submit-site-url">https://www.bing.com/toolbox/submit-site-url</a></li><li>360搜索引擎登录入口：<br /><a href="http://info.so.360.cn/site_submit.html">http://info.so.360.cn/site_submit.html</a></li><li>搜狗提交入口:<br /><a href="http://www.sogou.com/feedback/urlfeedback.php">http://www.sogou.com/feedback/urlfeedback.php</a></li></ul><p>目前，我所知道的就只有，如果你有其他搜索引擎的提交地址，可以在评论区中留下搜索引擎名称和地址，万分感谢！</p>]]>
                    </description>
                    <pubDate>Tue, 08 Feb 2022 18:26:58 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣1219:黄金矿工]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣1219黄金矿工</link>
                    <description>
                            <![CDATA[<p>2022年02月05日 力扣每日一题</p><h1 id="题目">题目</h1><p>你要开发一座金矿，地质勘测学家已经探明了这座金矿中的资源分布，并用大小为&nbsp;<code>m * n</code> 的网格 <code>grid</code> 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量；如果该单元格是空的，那么就是 <code>0</code>。</p><p>为了使收益最大化，矿工需要按以下规则来开采黄金：</p><ul><li>每当矿工进入一个单元，就会收集该单元格中的所有黄金。</li><li>矿工每次可以从当前位置向上下左右四个方向走。</li><li>每个单元格只能被开采（进入）一次。</li><li><strong>不得开采</strong>（进入）黄金数目为 <code>0</code> 的单元格。</li><li>矿工可以从网格中 <strong>任意一个</strong> 有黄金的单元格出发或者是停止。</li></ul><p>&nbsp;</p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>grid = [[0,6,0],[5,8,7],[0,9,0]]<strong>输出：</strong>24<strong>解释：</strong>[[0,6,0], [5,8,7], [0,9,0]]一种收集最多黄金的路线是：9 -&gt; 8 -&gt; 7。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>grid = [[1,0,7],[2,0,6],[3,4,5],[0,3,0],[9,0,20]]<strong>输出：</strong>28<strong>解释：</strong>[[1,0,7], [2,0,6], [3,4,5], [0,3,0], [9,0,20]]一种收集最多黄金的路线是：1 -&gt; 2 -&gt; 3 -&gt; 4 -&gt; 5 -&gt; 6 -&gt; 7。</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= grid.length,&nbsp;grid[i].length &lt;= 15</code></li><li><code>0 &lt;= grid[i][j] &lt;= 100</code></li><li>最多 <strong>25 </strong>个单元格中有黄金。</li></ul><div><div>Related Topics</div><div><li>数组</li><li>回溯</li><li>矩阵</li></div></div><h1 id="个人解法">个人解法</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    int[] xl = new int[]{1, -1, 0, 0};    int[] yl = new int[]{0, 0, 1, -1};    public int getMaximumGold(int[][] grid) {        int counts = 0;        boolean[][] use = new boolean[grid.length][grid[0].length];        for (int i = 0; i &lt; grid.length; i++) {            for (int j = 0; j &lt; grid[0].length; j++) {                use[i][j] = true;                counts = Math.max(counts, dfs(i, j, grid, use));                use[i][j] = false;            }        }        return counts;    }    private int dfs(int x, int y, int[][] grid, boolean[][] use) {        int counts = grid[x][y];        for (int i = 0; i &lt; 4; i++) {            int nx = x + xl[i];            int ny = y + yl[i];            if (nx &lt; 0 || nx &gt;= grid.length || ny &lt; 0 || ny &gt;= grid[0].length || grid[nx][ny] == 0 || use[nx][ny]) {                continue;            }            use[nx][ny] = true;            counts = Math.max(counts, grid[x][y] + dfs(nx, ny, grid, use));            use[nx][ny] = false;        }        return counts;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">class Solution:    def getMaximumGold(self, grid: List[List[int]]) -&gt; int:        def dfs(x: int, y: int) -&gt; int:            count = grid[x][y]            for nx, ny in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):                if nx &lt; 0 or nx &gt;= len(grid) or ny &lt; 0 or ny &gt;= len(grid[0]) or grid[nx][ny] == 0 or use[nx][ny]:                    continue                use[nx][ny] = True                count = max(count, grid[x][y] + dfs(nx, ny))                use[nx][ny] = False            return count        counts = 0        # 这种形式下，给一个元素赋值，对应的所有行相同列都会赋值        # use = [[False] * len(grid[0])] * len(grid)        use = [[False] * len(grid[0]) for _ in range(len(grid))]        for i in range(len(grid)):            for j in range(len(grid[0])):                if grid[i][j] != 0:                    use[i][j] = True                    counts = max(counts, dfs(i, j))                    use[i][j] = False        return counts</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Sun, 06 Feb 2022 12:37:26 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[seata1.4.1服务端部署及应用]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/seata141服务端部署及应用</link>
                    <description>
                            <![CDATA[<h1 id="seata141服务端部署及应用">seata1.4.1服务端部署及应用</h1><p>springcloud-nacos-seata</p><p><strong>分布式事务组件seata的使用demo，AT模式，集成nacos、springboot、springcloud、mybatis-plus、feign，数据库采用mysql</strong></p><p>demo中使用的相关版本号，具体请看代码。如果搭建个人demo不成功，验证是否是由版本导致，由于目前这几个项目更新比较频繁，版本稍有变化便会出现许多奇怪问题</p><ul><li>seata 1.4.1</li><li>spring-cloud-alibaba-seata 2.2.0.RELEASE</li><li>spring-cloud-starter-alibaba-nacos-discovery  2.1.1.RELEASE</li><li>springboot 2.1.10.RELEASE</li><li>springcloud Greenwich.SR4</li></ul><hr /><h1 id="1-服务端配置">1. 服务端配置</h1><p>seata-server为release版本1.4.1，采用docker部署方式</p><p><a href="https://github.com/seata/seata/releases/tag/v1.4.1">https://github.com/seata/seata/releases/tag/v1.4.1</a>)</p><h2 id="11-docker拉取镜像">1.1 docker拉取镜像</h2><pre><code class="language-shell">docker pull seataio/seata-server:1.4.1</code></pre><h2 id="12-启动临时容器">1.2 启动临时容器</h2><pre><code class="language-shell">docker run --rm --name seata-server -d -p 8091:8091 seataio/seata-server:1.4.1</code></pre><p><img src="https://file.huangge1199.cn/group1/M00/00/00/CgAYB2H54oaAImNHAAAUub2sTS4448.png" alt="" /></p><h2 id="13-将配置文件拷贝出来">1.3 将配置文件拷贝出来</h2><pre><code class="language-shell">docker cp d5cd81d60189:/seata-server/resources/ ./conf/</code></pre><h2 id="14-修改confregistryconf文件">1.4 修改conf/registry.conf文件</h2><p>修改文件，用nacos做注册中心和配置中心</p><pre><code class="language-shell">vi ./conf/registry.conf</code></pre><p>原始内容：</p><pre><code class="language-java">registry {  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa  type = &quot;file&quot;  loadBalance = &quot;RandomLoadBalance&quot;  loadBalanceVirtualNodes = 10  nacos {    application = &quot;seata-server&quot;    serverAddr = &quot;127.0.0.1:8848&quot;    group = &quot;SEATA_GROUP&quot;    namespace = &quot;&quot;    cluster = &quot;default&quot;    username = &quot;&quot;    password = &quot;&quot;  }  eureka {    serviceUrl = &quot;http://localhost:8761/eureka&quot;    application = &quot;default&quot;    weight = &quot;1&quot;  }  redis {    serverAddr = &quot;localhost:6379&quot;    db = 0    password = &quot;&quot;    cluster = &quot;default&quot;    timeout = 0  }  zk {    cluster = &quot;default&quot;    serverAddr = &quot;127.0.0.1:2181&quot;    sessionTimeout = 6000    connectTimeout = 2000    username = &quot;&quot;    password = &quot;&quot;  }  consul {    cluster = &quot;default&quot;    serverAddr = &quot;127.0.0.1:8500&quot;  }  etcd3 {    cluster = &quot;default&quot;    serverAddr = &quot;http://localhost:2379&quot;  }  sofa {    serverAddr = &quot;127.0.0.1:9603&quot;    application = &quot;default&quot;    region = &quot;DEFAULT_ZONE&quot;    datacenter = &quot;DefaultDataCenter&quot;    cluster = &quot;default&quot;    group = &quot;SEATA_GROUP&quot;    addressWaitTime = &quot;3000&quot;  }  file {    name = &quot;file.conf&quot;  }}config {  # file、nacos 、apollo、zk、consul、etcd3  type = &quot;file&quot;  nacos {    serverAddr = &quot;127.0.0.1:8848&quot;    namespace = &quot;&quot;    group = &quot;SEATA_GROUP&quot;    username = &quot;&quot;    password = &quot;&quot;  }  consul {    serverAddr = &quot;127.0.0.1:8500&quot;  }  apollo {    appId = &quot;seata-server&quot;    apolloMeta = &quot;http://192.168.1.204:8801&quot;    namespace = &quot;application&quot;    apolloAccesskeySecret = &quot;&quot;  }  zk {    serverAddr = &quot;127.0.0.1:2181&quot;    sessionTimeout = 6000    connectTimeout = 2000    username = &quot;&quot;    password = &quot;&quot;  }  etcd3 {    serverAddr = &quot;http://localhost:2379&quot;  }  file {    name = &quot;file.conf&quot;  }}</code></pre><p>修改后的内容：</p><pre><code class="language-java">registry {  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa  type = &quot;nacos&quot; # 改为nacos  loadBalance = &quot;RandomLoadBalance&quot;  loadBalanceVirtualNodes = 10  nacos {    application = &quot;seata-server&quot;    serverAddr = &quot;IP:端口&quot; # 改为nacos实际的IP:端口    group = &quot;SEATA_GROUP&quot;    namespace = &quot;&quot;    cluster = &quot;default&quot;    username = &quot;nacos&quot; # 改为nacos的账号    password = &quot;nacos&quot; # 改为nacos的密码  }  eureka {    serviceUrl = &quot;http://localhost:8761/eureka&quot;    application = &quot;default&quot;    weight = &quot;1&quot;  }  redis {    serverAddr = &quot;localhost:6379&quot;    db = 0    password = &quot;&quot;    cluster = &quot;default&quot;    timeout = 0  }  zk {    cluster = &quot;default&quot;    serverAddr = &quot;127.0.0.1:2181&quot;    sessionTimeout = 6000    connectTimeout = 2000    username = &quot;&quot;    password = &quot;&quot;  }  consul {    cluster = &quot;default&quot;    serverAddr = &quot;127.0.0.1:8500&quot;  }  etcd3 {    cluster = &quot;default&quot;    serverAddr = &quot;http://localhost:2379&quot;  }  sofa {    serverAddr = &quot;127.0.0.1:9603&quot;    application = &quot;default&quot;    region = &quot;DEFAULT_ZONE&quot;    datacenter = &quot;DefaultDataCenter&quot;    cluster = &quot;default&quot;    group = &quot;SEATA_GROUP&quot;    addressWaitTime = &quot;3000&quot;  }  file {    name = &quot;file.conf&quot;  }}config {  # file、nacos 、apollo、zk、consul、etcd3  type = &quot;nacos&quot; # 改为nacos  nacos {    serverAddr = &quot;IP:端口&quot; # 改为nacos实际的IP:端口    namespace = &quot;&quot;    group = &quot;SEATA_GROUP&quot;    username = &quot;nacos&quot; # 改为nacos的账号    password = &quot;nacos&quot; # 改为nacos的密码  }  consul {    serverAddr = &quot;127.0.0.1:8500&quot;  }  apollo {    appId = &quot;seata-server&quot;    apolloMeta = &quot;http://192.168.1.204:8801&quot;    namespace = &quot;application&quot;    apolloAccesskeySecret = &quot;&quot;  }  zk {    serverAddr = &quot;127.0.0.1:2181&quot;    sessionTimeout = 6000    connectTimeout = 2000    username = &quot;&quot;    password = &quot;&quot;  }  etcd3 {    serverAddr = &quot;http://localhost:2379&quot;  }  file {    name = &quot;file.conf&quot;  }}</code></pre><h2 id="15-执行sql语句">1.5 执行SQL语句</h2><p>seata配置使用db事务日志存储方式</p><p>SQL文件下载地址：<a href="https://github.com/seata/seata/tree/develop/script/server/db">seata/script/server/db at develop · seata/seata (github.com)</a></p><h2 id="16-创建configtxt并修改">1.6 创建config.txt并修改</h2><p>config.txt文件地址：<a href="https://github.com/seata/seata/blob/develop/script/config-center/config.txt">seata/config.txt at develop · seata/seata (github.com)</a></p><p>config.txt原件：</p><pre><code class="language-properties">transport.type=TCPtransport.server=NIOtransport.heartbeat=truetransport.enableClientBatchSendRequest=truetransport.threadFactory.bossThreadPrefix=NettyBosstransport.threadFactory.workerThreadPrefix=NettyServerNIOWorkertransport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandlertransport.threadFactory.shareBossWorker=falsetransport.threadFactory.clientSelectorThreadPrefix=NettyClientSelectortransport.threadFactory.clientSelectorThreadSize=1transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThreadtransport.threadFactory.bossThreadSize=1transport.threadFactory.workerThreadSize=defaulttransport.shutdown.wait=3service.vgroupMapping.my_test_tx_group=defaultservice.default.grouplist=127.0.0.1:8091service.enableDegrade=falseservice.disableGlobalTransaction=falseclient.rm.asyncCommitBufferLimit=10000client.rm.lock.retryInterval=10client.rm.lock.retryTimes=30client.rm.lock.retryPolicyBranchRollbackOnConflict=trueclient.rm.reportRetryCount=5client.rm.tableMetaCheckEnable=falseclient.rm.tableMetaCheckerInterval=60000client.rm.sqlParserType=druidclient.rm.reportSuccessEnable=falseclient.rm.sagaBranchRegisterEnable=falseclient.rm.tccActionInterceptorOrder=-2147482648client.tm.commitRetryCount=5client.tm.rollbackRetryCount=5client.tm.defaultGlobalTransactionTimeout=60000client.tm.degradeCheck=falseclient.tm.degradeCheckAllowTimes=10client.tm.degradeCheckPeriod=2000client.tm.interceptorOrder=-2147482648store.mode=filestore.lock.mode=filestore.session.mode=filestore.publicKey=store.file.dir=file_store/datastore.file.maxBranchSessionSize=16384store.file.maxGlobalSessionSize=512store.file.fileWriteBufferCacheSize=16384store.file.flushDiskMode=asyncstore.file.sessionReloadReadSize=100store.db.datasource=druidstore.db.dbType=mysqlstore.db.driverClassName=com.mysql.jdbc.Driverstore.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&amp;rewriteBatchedStatements=truestore.db.user=usernamestore.db.password=passwordstore.db.minConn=5store.db.maxConn=30store.db.globalTable=global_tablestore.db.branchTable=branch_tablestore.db.queryLimit=100store.db.lockTable=lock_tablestore.db.maxWait=5000store.redis.mode=singlestore.redis.single.host=127.0.0.1store.redis.single.port=6379store.redis.sentinel.masterName=store.redis.sentinel.sentinelHosts=store.redis.maxConn=10store.redis.minConn=1store.redis.maxTotal=100store.redis.database=0store.redis.password=store.redis.queryLimit=100server.recovery.committingRetryPeriod=1000server.recovery.asynCommittingRetryPeriod=1000server.recovery.rollbackingRetryPeriod=1000server.recovery.timeoutRetryPeriod=1000server.maxCommitRetryTimeout=-1server.maxRollbackRetryTimeout=-1server.rollbackRetryTimeoutUnlockEnable=falseserver.distributedLockExpireTime=10000client.undo.dataValidation=trueclient.undo.logSerialization=jacksonclient.undo.onlyCareUpdateColumns=trueserver.undo.logSaveDays=7server.undo.logDeletePeriod=86400000client.undo.logTable=undo_logclient.undo.compress.enable=trueclient.undo.compress.type=zipclient.undo.compress.threshold=64klog.exceptionRate=100transport.serialization=seatatransport.compressor=nonemetrics.enabled=falsemetrics.registryType=compactmetrics.exporterList=prometheusmetrics.exporterPrometheusPort=9898</code></pre><p>这里根据自己需求做调整，我这里的配置如下：</p><pre><code class="language-properties">service.vgroupMapping.order-service-group=defaultservice.vgroupMapping.storage-service-group=defaultservice.enableDegrade=falseservice.disableGlobalTransaction=falsestore.mode=dbstore.db.datasource=druidstore.db.dbType=mysql#store.db.driverClassName=com.mysql.jdbc.Driver 这个是mysql8以下的驱动store.db.driverClassName=com.mysql.cj.jdbc.Driver #这个是mysql8的驱动store.db.url=jdbc:mysql://192.168.0.1:3306/seata?useUnicode=true #这个是mysql的连接信息store.db.user=root #这个是mysql的用户名store.db.password=123456 #这个是mysql的密码store.db.minConn=5store.db.maxConn=30store.db.globalTable=global_tablestore.db.branchTable=branch_tablestore.db.queryLimit=100store.db.lockTable=lock_tablestore.db.maxWait=5000</code></pre><h2 id="17-创建nacos-configsh">1.7 创建nacos-config.sh</h2><p>在conf中</p><p>nacos-config.sh获取地址：<a href="https://github.com/seata/seata/tree/develop/script/config-center/nacos">seata/script/config-center/nacos at develop · seata/seata (github.com)</a></p><h2 id="18-上传seata配置信息到nacos">1.8 上传seata配置信息到nacos</h2><p>先确认目录结构正确</p><p><img src="https://file.huangge1199.cn/group1/M00/00/00/CgAYB2H54zyAYKwlAAAi_TSko4g044.png" alt="" /></p><pre><code class="language-sh">./nacos-config.sh -h docker所在机器IP -p 8848 -g SEATA_GROUP  -u nacos -w nacos</code></pre><h4 id="122-修改confnacos-configtxt-配置">1.2.2 修改conf/nacos-config.txt 配置</h4><p><span your-service-gruop="">service.vgroup_mapping.$</span><span your-service-gruop="">=default，中间的$</span>为自己定义的服务组名称，服务中的application.properties文件里配置服务组名称。</p><p>demo中有两个服务，分别是storage-service和order-service，所以配置如下</p><pre><code class="language-properties">service.vgroup_mapping.storage-service-group=defaultservice.vgroup_mapping.order-service-group=default</code></pre><p>** 注意这里,高版本中应该是vgroupMapping 同时后面的如: order-service-group  不能定义为 order_service_group**</p><h4 id="13-启动seata-server">1.3 启动seata-server</h4><p><strong>分两步，如下</strong></p><pre><code class="language-shell"># 初始化seata 的nacos配置cd confsh nacos-config.sh 192.168.21.89# 启动seata-servercd binsh seata-server.sh -p 8091 -m file</code></pre><hr /><h1 id="2-应用配置">2. 应用配置</h1><h3 id="21-数据库初始化">2.1 数据库初始化</h3><pre><code class="language-SQL">-- 创建 order库、业务表、undo_log表create database seata_order;use seata_order;DROP TABLE IF EXISTS `order_tbl`;CREATE TABLE `order_tbl` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `user_id` varchar(255) DEFAULT NULL,  `commodity_code` varchar(255) DEFAULT NULL,  `count` int(11) DEFAULT 0,  `money` int(11) DEFAULT 0,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `undo_log`(  `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT,  `branch_id`     BIGINT(20)   NOT NULL,  `xid`           VARCHAR(100) NOT NULL,  `context`       VARCHAR(128) NOT NULL,  `rollback_info` LONGBLOB     NOT NULL,  `log_status`    INT(11)      NOT NULL,  `log_created`   DATETIME     NOT NULL,  `log_modified`  DATETIME     NOT NULL,  `ext`           VARCHAR(100) DEFAULT NULL,  PRIMARY KEY (`id`),  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)) ENGINE = InnoDB  AUTO_INCREMENT = 1  DEFAULT CHARSET = utf8;-- 创建 storage库、业务表、undo_log表create database seata_storage;use seata_storage;DROP TABLE IF EXISTS `storage_tbl`;CREATE TABLE `storage_tbl` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `commodity_code` varchar(255) DEFAULT NULL,  `count` int(11) DEFAULT 0,  PRIMARY KEY (`id`),  UNIQUE KEY (`commodity_code`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `undo_log`(  `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT,  `branch_id`     BIGINT(20)   NOT NULL,  `xid`           VARCHAR(100) NOT NULL,  `context`       VARCHAR(128) NOT NULL,  `rollback_info` LONGBLOB     NOT NULL,  `log_status`    INT(11)      NOT NULL,  `log_created`   DATETIME     NOT NULL,  `log_modified`  DATETIME     NOT NULL,  `ext`           VARCHAR(100) DEFAULT NULL,  PRIMARY KEY (`id`),  UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)) ENGINE = InnoDB  AUTO_INCREMENT = 1  DEFAULT CHARSET = utf8;-- 初始化库存模拟数据INSERT INTO seata_storage.storage_tbl (id, commodity_code, count) VALUES (1, 'product-1', 9999999);INSERT INTO seata_storage.storage_tbl (id, commodity_code, count) VALUES (2, 'product-2', 0);</code></pre><h3 id="22-应用配置">2.2 应用配置</h3><p>见代码</p><p>几个重要的配置</p><ol><li>每个应用的resource里需要配置一个registry.conf ，demo中与seata-server里的配置相同</li><li><span your-service-gruop="">application.propeties 的各个配置项，注意spring.cloud.alibaba.seata.tx-service-group 是服务组名称，与nacos-config.txt 配置的service.vgroup_mapping.$</span>具有对应关系</li></ol><hr /><h1 id="3-测试">3. 测试</h1><ol><li><p>分布式事务成功，模拟正常下单、扣库存</p><p>localhost:9091/order/placeOrder/commit</p></li><li><p>分布式事务失败，模拟下单成功、扣库存失败，最终同时回滚</p><p>localhost:9091/order/placeOrder/rollback</p></li></ol>]]>
                    </description>
                    <pubDate>Wed, 02 Feb 2022 09:37:59 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣1688:比赛中的配对次数]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣1688比赛中的配对次数</link>
                    <description>
                            <![CDATA[<p>2022年01月25日 力扣每日一题</p><h1 id="题目">题目</h1><p>给你一个整数 <code>n</code> ，表示比赛中的队伍数。比赛遵循一种独特的赛制：</p><ul><li>如果当前队伍数是 <strong>偶数</strong> ，那么每支队伍都会与另一支队伍配对。总共进行 <code>n / 2</code> 场比赛，且产生 <code>n / 2</code> 支队伍进入下一轮。</li><li>如果当前队伍数为 <strong>奇数</strong> ，那么将会随机轮空并晋级一支队伍，其余的队伍配对。总共进行 <code>(n - 1) / 2</code> 场比赛，且产生 <code>(n - 1) / 2 + 1</code> 支队伍进入下一轮。</li></ul><p>返回在比赛中进行的配对次数，直到决出获胜队伍为止。</p><p> </p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>n = 7<strong>输出：</strong>6<strong>解释：</strong>比赛详情：- 第 1 轮：队伍数 = 7 ，配对次数 = 3 ，4 支队伍晋级。- 第 2 轮：队伍数 = 4 ，配对次数 = 2 ，2 支队伍晋级。- 第 3 轮：队伍数 = 2 ，配对次数 = 1 ，决出 1 支获胜队伍。总配对次数 = 3 + 2 + 1 = 6</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>n = 14<strong>输出：</strong>13<strong>解释：</strong>比赛详情：- 第 1 轮：队伍数 = 14 ，配对次数 = 7 ，7 支队伍晋级。- 第 2 轮：队伍数 = 7 ，配对次数 = 3 ，4 支队伍晋级。 - 第 3 轮：队伍数 = 4 ，配对次数 = 2 ，2 支队伍晋级。- 第 4 轮：队伍数 = 2 ，配对次数 = 1 ，决出 1 支获胜队伍。总配对次数 = 7 + 3 + 2 + 1 = 13</pre><p> </p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= n &lt;= 200</code></li></ul><div><div>Related Topics</div><div><li>数学</li><li>模拟</li></div></div><h1 id="个人解法">个人解法</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public int numberOfMatches(int n) {        // 总配对次数        int sum = 0;        while (n &gt; 1) {            if (n % 2 == 1) {                // 奇数队伍                // 配对次数：(n - 1) / 2                sum += (n - 1) / 2;                // 剩余队伍数：(n - 1) / 2 + 1                n = (n - 1) / 2 + 1;            } else {                // 偶数队伍                // 配对次数：n / 2                sum += n / 2;                // 剩余队伍数：n / 2                n /= 2;            }        }        return sum;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">class Solution:    def numberOfMatches(self, n: int) -&gt; int:        # 总配对次数        sums = 0        while n &gt; 1:            if n % 2 == 1:                # 奇数队伍                # 配对次数：(n - 1) / 2                sums += (n - 1) / 2                # 剩余队伍数：(n - 1) / 2 + 1                n = (n - 1) / 2 + 1            else:                # 偶数队伍                # 配对次数：n / 2                sums += n / 2                # 剩余队伍数：n / 2                n /= 2        return int(sums)</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Tue, 25 Jan 2022 13:57:48 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2045:到达目的地的第二短时间]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2045到达目的地的第二短时间</link>
                    <description>
                            <![CDATA[<p>2022年01月24日 力扣每日一题</p><h1 id="题目">题目</h1><p>城市用一个 <strong>双向连通</strong> 图表示，图中有 <code>n</code> 个节点，从 <code>1</code> 到 <code>n</code> 编号（包含 <code>1</code> 和 <code>n</code>）。图中的边用一个二维整数数组 <code>edges</code> 表示，其中每个 <code>edges[i] = [u<sub>i</sub>, v<sub>i</sub>]</code>&nbsp;表示一条节点&nbsp;<code>u<sub>i</sub></code> 和节点&nbsp;<code>v<sub>i</sub></code> 之间的双向连通边。每组节点对由 <strong>最多一条</strong> 边连通，顶点不存在连接到自身的边。穿过任意一条边的时间是 <code>time</code>&nbsp;分钟。</p><p>每个节点都有一个交通信号灯，每 <code>change</code> 分钟改变一次，从绿色变成红色，再由红色变成绿色，循环往复。所有信号灯都&nbsp;<strong>同时</strong> 改变。你可以在 <strong>任何时候</strong> 进入某个节点，但是 <strong>只能</strong> 在节点&nbsp;<strong>信号灯是绿色时</strong> 才能离开。如果信号灯是&nbsp; <strong>绿色</strong> ，你 <strong>不能</strong> 在节点等待，必须离开。</p><p><strong>第二小的值</strong> 是&nbsp;<strong>严格大于</strong> 最小值的所有值中最小的值。</p><ul><li>例如，<code>[2, 3, 4]</code> 中第二小的值是 <code>3</code> ，而 <code>[2, 2, 4]</code> 中第二小的值是 <code>4</code> 。</li></ul><p>给你 <code>n</code>、<code>edges</code>、<code>time</code> 和 <code>change</code> ，返回从节点 <code>1</code> 到节点 <code>n</code> 需要的 <strong>第二短时间</strong> 。</p><p><strong>注意：</strong></p><ul><li>你可以 <strong>任意次</strong> 穿过任意顶点，<strong>包括</strong> <code>1</code> 和 <code>n</code> 。</li><li>你可以假设在 <strong>启程时</strong> ，所有信号灯刚刚变成 <strong>绿色</strong> 。</li></ul><p>&nbsp;</p><p><strong>示例 1：</strong></p><p><img alt="" src="https://assets.leetcode.com/uploads/2021/09/29/e1.png" style="width: 200px; height: 250px;" />        <img alt="" src="https://assets.leetcode.com/uploads/2021/09/29/e2.png" style="width: 200px; height: 250px;" /></p><pre><strong>输入：</strong>n = 5, edges = [[1,2],[1,3],[1,4],[3,4],[4,5]], time = 3, change = 5<strong>输出：</strong>13<strong>解释：</strong>上面的左图展现了给出的城市交通图。右图中的蓝色路径是最短时间路径。花费的时间是：- 从节点 1 开始，总花费时间=0- 1 -&gt; 4：3 分钟，总花费时间=3- 4 -&gt; 5：3 分钟，总花费时间=6因此需要的最小时间是 6 分钟。右图中的红色路径是第二短时间路径。- 从节点 1 开始，总花费时间=0- 1 -&gt; 3：3 分钟，总花费时间=3- 3 -&gt; 4：3 分钟，总花费时间=6- 在节点 4 等待 4 分钟，总花费时间=10- 4 -&gt; 5：3 分钟，总花费时间=13因此第二短时间是 13 分钟。      </pre><p><strong>示例 2：</strong></p><p><img alt="" src="https://assets.leetcode.com/uploads/2021/09/29/eg2.png" style="width: 225px; height: 50px;" /></p><pre><strong>输入：</strong>n = 2, edges = [[1,2]], time = 3, change = 2<strong>输出：</strong>11<strong>解释：</strong>最短时间路径是 1 -&gt; 2 ，总花费时间 = 3 分钟最短时间路径是 1 -&gt; 2 -&gt; 1 -&gt; 2 ，总花费时间 = 11 分钟</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>2 &lt;= n &lt;= 10<sup>4</sup></code></li><li><code>n - 1 &lt;= edges.length &lt;= min(2 * 10<sup>4</sup>, n * (n - 1) / 2)</code></li><li><code>edges[i].length == 2</code></li><li><code>1 &lt;= u<sub>i</sub>, v<sub>i</sub> &lt;= n</code></li><li><code>u<sub>i</sub> != v<sub>i</sub></code></li><li>不含重复边</li><li>每个节点都可以从其他节点直接或者间接到达</li><li><code>1 &lt;= time, change &lt;= 10<sup>3</sup></code></li></ul><div><div>Related Topics</div><div><li>广度优先搜索</li><li>图</li><li>最短路</li></div></div><h1 id="个人解法">个人解法</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">import java.util.*;class Solution {    public int secondMinimum(int n, int[][] edges, int time, int change) {        // 统计所有节点的联通节点，并将其存入map中留着后面使用        Map&lt;Integer, List&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;(n);        for (int i = 1; i &lt;= n; i++) {            map.put(i, new ArrayList&lt;&gt;());        }        for (int[] edge : edges) {            map.get(edge[0]).add(edge[1]);            map.get(edge[1]).add(edge[0]);        }        Queue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();        queue.add(1);        // 记录节点到达的次数        int[] counts = new int[n + 1];        // 记录到达节点的时间        int free = 0;        while (!queue.isEmpty()) {            // 红灯情况下加上需要等待的时间            if (free % (2 * change) &gt;= change) {                free += change - free % change;            }            free += time;            // 同一时间可以到达的节点数量            int size = queue.size();            // 同一时间节点是否已经到达            boolean[] use = new boolean[n + 1];            for (int i = 0; i &lt; size; i++) {                // 获取该节点接下来可以到达的节点                List&lt;Integer&gt; list = map.get(queue.poll());                for (int num : list) {                    // 同一时间未到达，并且到达该节点的总次数小于2                    if (!use[num] &amp;&amp; counts[num] &lt; 2) {                        queue.add(num);                        use[num] = true;                        counts[num]++;                    }                    // 如果是第二次到达最后一个节点，直接返回需要到达的诗句                    if (num == n &amp;&amp; counts[num] == 2) {                        return free;                    }                }            }        }        return 0;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from collections import dequefrom typing import Listclass Solution:    def secondMinimum(self, n: int, edges: List[List[int]], time: int, change: int) -&gt; int:        # 统计所有节点的联通节点，并将其存入map中留着后面使用        maps = [[0] for _ in range(n + 1)]        for edge in edges:            maps[edge[0]].append(edge[1])            maps[edge[1]].append(edge[0])        queue = deque()        queue.append(1)        # 记录节点到达的次数        counts = [0] * (n + 1)        # 记录到达节点的时间        free = 0        while len(queue):            # 红灯情况下加上需要等待的时间            if free % (2 * change) &gt;= change:                free += change - free % change            free += time            # 同一时间可以到达的节点数量            size = len(queue)            # 同一时间节点是否已经到达            use = [False] * (n + 1)            for i in range(size):                for num in maps[queue.popleft()]:                    # 同一时间未到达，并且到达该节点的总次数小于2                    if use[num] is False and counts[num] &lt; 2:                        queue.append(num)                        use[num] = True                        counts[num] += 1                    # 如果是第二次到达最后一个节点，直接返回需要到达的诗句                    if num == n and counts[num] == 2:                        return free        return 0</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Mon, 24 Jan 2022 15:22:58 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣1345:跳跃游戏 IV]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣1345跳跃游戏iv</link>
                    <description>
                            <![CDATA[<p>2022年01月21日 力扣每日一题</p><h1 id="题目">题目</h1><p>给你一个整数数组&nbsp;<code>arr</code>&nbsp;，你一开始在数组的第一个元素处（下标为 0）。</p><p>每一步，你可以从下标&nbsp;<code>i</code>&nbsp;跳到下标：</p><ul><li><code>i + 1</code>&nbsp;满足：<code>i + 1 &lt; arr.length</code></li><li><code>i - 1</code>&nbsp;满足：<code>i - 1 &gt;= 0</code></li><li><code>j</code>&nbsp;满足：<code>arr[i] == arr[j]</code>&nbsp;且&nbsp;<code>i != j</code></li></ul><p>请你返回到达数组最后一个元素的下标处所需的&nbsp;<strong>最少操作次数</strong>&nbsp;。</p><p>注意：任何时候你都不能跳到数组外面。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>arr = [100,-23,-23,404,100,23,23,23,3,404]<strong>输出：</strong>3<strong>解释：</strong>那你需要跳跃 3 次，下标依次为 0 --&gt; 4 --&gt; 3 --&gt; 9 。下标 9 为数组的最后一个元素的下标。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>arr = [7]<strong>输出：</strong>0<strong>解释：</strong>一开始就在最后一个元素处，所以你不需要跳跃。</pre><p><strong>示例 3：</strong></p><pre><strong>输入：</strong>arr = [7,6,9,6,9,6,9,7]<strong>输出：</strong>1<strong>解释：</strong>你可以直接从下标 0 处跳到下标 7 处，也就是数组的最后一个元素处。</pre><p><strong>示例 4：</strong></p><pre><strong>输入：</strong>arr = [6,1,9]<strong>输出：</strong>2</pre><p><strong>示例 5：</strong></p><pre><strong>输入：</strong>arr = [11,22,7,7,7,7,7,7,7,22,13]<strong>输出：</strong>3</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= arr.length &lt;= 5 * 10^4</code></li><li><code>-10^8 &lt;= arr[i] &lt;= 10^8</code></li></ul><div><div>Related Topics</div><div><li>广度优先搜索</li><li>数组</li><li>哈希表</li></div></div><h1 id="个人解法">个人解法</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">import java.util.*;class Solution {    public int minJumps(int[] arr) {        if (arr.length == 1) {            return 0;        }        boolean[] use = new boolean[arr.length];        Map&lt;Integer, List&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;();        for (int i = 0; i &lt; arr.length; i++) {            map.computeIfAbsent(arr[i], k -&gt; new ArrayList&lt;&gt;        }        use[0] = true;        Queue&lt;Integer&gt; queue = new ArrayDeque&lt;&gt;();        queue.add(0);        int count = 0;        while (!queue.isEmpty()) {            int size = queue.size();            count++;            for (int i = 0; i &lt; size; i++) {                int index = queue.poll();                if (index - 1 &gt;= 0 &amp;&amp; !use[index - 1]) {                    queue.add(index - 1);                    use[index - 1] = true;                }                if (index + 1 == arr.length - 1) {                    return count;                }                if (index + 1 &gt;= 0 &amp;&amp; !use[index + 1]) {                    queue.add(index + 1);                    use[index + 1] = true;                }                if (map.containsKey(arr[index])) {                    List&lt;Integer&gt; list = map.get(arr[index])                    map.remove(arr[index]);                    for (int ind : list) {                        if (ind == arr.length - 1) {                            return count;                        }                        if (!use[ind]) {                            queue.add(ind);                            use[ind] = true;                        }                    }                }            }        }        return 0;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from collections import defaultdict, dequefrom typing import Listclass Solution:    def minJumps(self, arr: List[int]) -&gt; int:        if len(arr) == 1:            return 0        map = defaultdict(list)        for i, a in enumerate(arr):            map[a].append(i)        use = set()        queue = deque()        queue.append(0)        use.add(0)        count = 0        while queue:            count += 1            for i in range(len(queue)):                index = queue.popleft()                if index - 1 &gt;= 0 and (index - 1) not in use:                    use.add(index - 1)                    queue.append(index - 1)                if index + 1 == len(arr) - 1:                    return count                if index + 1 &lt; len(arr) and (index + 1) not in use:                    use.add(index + 1)                    queue.append(index + 1)                v = arr[index]                for i in map[v]:                    if i == len(arr) - 1:                        return count                    if i not in use:                        use.add(i)                        queue.append(i)                del map[v]</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Fri, 21 Jan 2022 16:26:26 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣2029:石子游戏 IX]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣2029石子游戏ix</link>
                    <description>
                            <![CDATA[<p>2022年01月20日 力扣每日一题</p><h1 id="题目">题目</h1><p>Alice 和 Bob 再次设计了一款新的石子游戏。现有一行 n 个石子，每个石子都有一个关联的数字表示它的价值。给你一个整数数组 <code>stones</code> ，其中 <code>stones[i]</code> 是第 <code>i</code> 个石子的价值。</p><p>Alice 和 Bob 轮流进行自己的回合，<strong>Alice</strong> 先手。每一回合，玩家需要从 <code>stones</code>&nbsp;中移除任一石子。</p><ul><li>如果玩家移除石子后，导致 <strong>所有已移除石子</strong> 的价值&nbsp;<strong>总和</strong> 可以被 3 整除，那么该玩家就 <strong>输掉游戏</strong> 。</li><li>如果不满足上一条，且移除后没有任何剩余的石子，那么 Bob 将会直接获胜（即便是在 Alice 的回合）。</li></ul><p>假设两位玩家均采用&nbsp;<strong>最佳</strong> 决策。如果 Alice 获胜，返回 <code>true</code> ；如果 Bob 获胜，返回 <code>false</code> 。</p><p>&nbsp;</p><p><strong>示例 1：</strong></p><pre><strong>输入：</strong>stones = [2,1]<strong>输出：</strong>true<strong>解释：</strong>游戏进行如下：- 回合 1：Alice 可以移除任意一个石子。- 回合 2：Bob 移除剩下的石子。 已移除的石子的值总和为 1 + 2 = 3 且可以被 3 整除。因此，Bob 输，Alice 获胜。</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>stones = [2]<strong>输出：</strong>false<strong>解释：</strong>Alice 会移除唯一一个石子，已移除石子的值总和为 2 。 由于所有石子都已移除，且值总和无法被 3 整除，Bob 获胜。</pre><p><strong>示例 3：</strong></p><pre><strong>输入：</strong>stones = [5,1,2,4,3]<strong>输出：</strong>false<strong>解释：</strong>Bob 总会获胜。其中一种可能的游戏进行方式如下：- 回合 1：Alice 可以移除值为 1 的第 2 个石子。已移除石子值总和为 1 。- 回合 2：Bob 可以移除值为 3 的第 5 个石子。已移除石子值总和为 = 1 + 3 = 4 。- 回合 3：Alices 可以移除值为 4 的第 4 个石子。已移除石子值总和为 = 1 + 3 + 4 = 8 。- 回合 4：Bob 可以移除值为 2 的第 3 个石子。已移除石子值总和为 = 1 + 3 + 4 + 2 = 10.- 回合 5：Alice 可以移除值为 5 的第 1 个石子。已移除石子值总和为 = 1 + 3 + 4 + 2 + 5 = 15.Alice 输掉游戏，因为已移除石子值总和（15）可以被 3 整除，Bob 获胜。</pre><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= stones.length &lt;= 10<sup>5</sup></code></li><li><code>1 &lt;= stones[i] &lt;= 10<sup>4</sup></code></li></ul><div><div>Related Topics</div><div><li>贪心</li><li>数组</li><li>数学</li><li>计数</li><li>博弈</li></div></div><h1 id="个人解法">个人解法</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">class Solution {    public boolean stoneGameIX(int[] stones) {        int[] counts = new int[3];        for (int stone : stones) {            counts[stone % 3]++;        }        return counts[0] % 2 == 0 ? counts[1] &gt; 0 &amp;&amp; counts[2] &gt; 0 : Math.abs(counts[1] - counts[2]) &gt; 2;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from typing import Listclass Solution:    def stoneGameIX(self, stones: List[int]) -&gt; bool:        counts = [0] * 3        for stone in stones:            counts[stone % 3] += 1        if counts[0] % 2 == 0:            return counts[1] &gt; 0 and counts[2] &gt; 0        else:            return abs(counts[1] - counts[2]) &gt; 2</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Thu, 20 Jan 2022 10:56:54 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[力扣219:存在重复元素 II]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/力扣219存在重复元素ii</link>
                    <description>
                            <![CDATA[<p>2022年01月19日 力扣每日一题</p><h1 id="题目">题目</h1><p>给你一个整数数组&nbsp;<code>nums</code> 和一个整数&nbsp;<code>k</code> ，判断数组中是否存在两个 <strong>不同的索引</strong><em>&nbsp;</em><code>i</code>&nbsp;和<em>&nbsp;</em><code>j</code> ，满足 <code>nums[i] == nums[j]</code> 且 <code>abs(i - j) &lt;= k</code> 。如果存在，返回 <code>true</code> ；否则，返回 <code>false</code> 。</p><p>&nbsp;</p><p><strong>示例&nbsp;1：</strong></p><pre><strong>输入：</strong>nums = [1,2,3,1], k<em> </em>= 3<strong>输出：</strong>true</pre><p><strong>示例 2：</strong></p><pre><strong>输入：</strong>nums = [1,0,1,1], k<em> </em>=<em> </em>1<strong>输出：</strong>true</pre><p><strong>示例 3：</strong></p><pre><strong>输入：</strong>nums = [1,2,3,1,2,3], k<em> </em>=<em> </em>2<strong>输出：</strong>false</pre><p>&nbsp;</p><p>&nbsp;</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= nums.length &lt;= 10<sup>5</sup></code></li><li><code>-10<sup>9</sup> &lt;= nums[i] &lt;= 10<sup>9</sup></code></li><li><code>0 &lt;= k &lt;= 10<sup>5</sup></code></li></ul><div><div>Related Topics</div><div><li>数组</li><li>哈希表</li><li>滑动窗口</li></div></div><h1 id="个人解法">个人解法</h1><p>{% tabs categories%}</p><!-- tab Java --><pre><code class="language-java">import java.util.HashMap;import java.util.Map;class Solution {    public boolean containsNearbyDuplicate(int[] nums, int k) {        if (k &lt;= 0) {            return false;        }        Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();        for (int i = 0; i &lt; nums.length; i++) {            if (map.containsKey(nums[i]) &amp;&amp; i - map.get(nums[i]) &lt;= k) {                return true;            }            map.put(nums[i], i);        }        return false;    }}</code></pre><!-- endtab --><!-- tab Python3 --><pre><code class="language-python">from typing import Listclass Solution:    def containsNearbyDuplicate(self, nums: List[int], k: int) -&gt; bool:        map = {}        for i, num in enumerate(nums):            if num in map and i - map[num] &lt;= k:                return True            map[num] = i        return False</code></pre><!-- endtab --><p>{% endtabs %}</p>]]>
                    </description>
                    <pubDate>Wed, 19 Jan 2022 11:24:37 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Sublime Text 4 破解]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/sublimetext4破解</link>
                    <description>
                            <![CDATA[<h1 id="下载地址">下载地址</h1><p><a href="https://www.sublimetext.com/download">https://www.sublimetext.com/download</a></p><h1 id="激活方法">激活方法</h1><h2 id="打开在线十六进制编辑器">打开在线十六进制编辑器</h2><p>地址：<a href="https://hexed.it/">hexed</a></p><p><img src="https://img.huangge1199.cn/blog/sublimeText4Purchase/image-20220114153619254.png" alt="image-20220114153619254" /></p><h2 id="打开sublime-textexe文件">打开<code>sublime_text.exe</code>文件</h2><p><img src="https://img.huangge1199.cn/blog/sublimeText4Purchase/img.png" alt="img.png" /></p><h2 id="替换">替换</h2><p>根据版本不同替换不同：</p><ul><li><p>X64版本</p><p><code>4157415656575553B828210000</code> 替换为 <code>33C0FEC0C3575553B828210000</code></p></li><li><p>X86版本<br /><code>55535756B8AC200000</code> 替换为 <code>33C0FEC0C3AC200000</code></p></li></ul><p>按住<code>Ctrl+F</code>，我这边是64位电脑，在搜索中输入<code>4157415656575553B828210000</code> ，在替换为输入<code>33C0FEC0C3AC200000</code>，如果替换为无法输入，记得将替换为上一行的启用替换勾选上，然后先查找一下，接下来再点击替换</p><p><img src="https://img.huangge1199.cn/blog/sublimeText4Purchase/image-20220114154528250.png" alt="image-20220114154528250" /></p><h2 id="替换后点击另存为替换掉原来的文件保存">替换后点击另存为，替换掉原来的文件，保存</h2><p><img src="https://img.huangge1199.cn/blog/sublimeText4Purchase/image-20220114154843108.png" alt="image-20220114154843108" /></p><h2 id="输入激活码激活">输入激活码激活</h2><ul><li><p>打开应用</p></li><li><p>依次点击Help-&gt;Enter License</p><p><img src="https://img.huangge1199.cn/blog/sublimeText4Purchase/image-20220114155104130.png" alt="image-20220114155104130" /></p></li><li><p>在弹出的窗口输入激活码</p><p>激活码如下：</p><pre><code class="language-tex">----- BEGIN LICENSE -----RUYO.netUnlimited User LicenseEA7E-810442300C0CD4A8 CAA317D9 CCABD1AC 434C984C7E4A0B13 77893C3E DD0A5BA1 B2EB721C4BAAB4C4 9B96437D 14EB743E 7DB55D9C7CA26EE2 67C3B4EC 29B2C65A 88D90C59CB6CCBA5 7DE6177B C02C2826 8C9A21B06AB1A5B6 20B09EA2 01C979BD 29670B1992DC6D90 6E365849 4AB84739 5B4C3EA1048CC1D0 9748ED54 CAC9D585 90CAD815------ END LICENSE ------</code></pre><p><img src="https://img.huangge1199.cn/blog/sublimeText4Purchase/image-20220114155245648.png" alt="image-20220114155245648" /></p></li><li><p>点击下方的Use License</p><p><img src="https://img.huangge1199.cn/blog/sublimeText4Purchase/image-20220114155341371.png" alt="image-20220114155341371" /></p></li><li><p>确认</p><p>依次点击Help-&gt;About Sublime Text</p><p><img src="https://img.huangge1199.cn/blog/sublimeText4Purchase/image-20220114155558214.png" alt="image-20220114155558214" /></p></li></ul>]]>
                    </description>
                    <pubDate>Fri, 14 Jan 2022 15:24:18 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[用nexus部署maven私服]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/用nexus部署maven私服</link>
                    <description>
                            <![CDATA[<h1 id="nexus-服务部署">nexus 服务部署</h1><p>由于本人习惯问题，本次继续用docker部署</p><h2 id="查找docker镜像">查找docker镜像</h2><p>通过<a href="https://hub.docker.com/">https://hub.docker.com/</a> 网站查找，选用了官方的sonatype/nexus3</p><h2 id="拉取镜像">拉取镜像</h2><pre><code class="language-sh">docker pull sonatype/nexus3</code></pre><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112202513617.png" alt="image-20220112202513617" /></p><h2 id="创建宿主机挂载目录并编写docker-composeyml">创建宿主机挂载目录并编写docker-compose.yml</h2><p>执行命令：</p><pre><code class="language-sh">vi docker-compose.ymlmkdir nexus-data</code></pre><p>docker-compose.yml内容：</p><pre><code class="language-sh">version: '3'services:    nexus3:        container_name: nexus3        image: sonatype/nexus3:latest        environment:            - TZ=Asia/Shanghai        volumes:             - ./nexus-data:/var/nexus-data        ports:             - 8081:8081        restart: always</code></pre><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112203927784.png" alt="image-20220112203927784" /></p><h2 id="启动容器">启动容器</h2><pre><code class="language-sh">docker-compose up -d</code></pre><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112204407509.png" alt="image-20220112204407509" /></p><h2 id="浏览器验证">浏览器验证</h2><p>浏览器中输入<a href="http://IP:8081/，出现下面的页面启动完成">http://IP:8081/，出现下面的页面启动完成</a></p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112204921432.png" alt="image-20220112204921432" /></p><h1 id="nexus-服务的配置">Nexus 服务的配置</h1><h2 id="浏览器中点击右上角的登录">浏览器中点击右上角的登录</h2><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112205557025.png" alt="image-20220112205557025" /></p><h2 id="登录">登录</h2><p>首次登录会提示密码保存在**/nexus-data/admin.password**（位置可能会变，看提示）</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112214506743.png" alt="image-20220112214506743" /></p><p>由于这个目录我们的docker并没有引出来，所以我们要去docker容器内查看</p><pre><code class="language-sh">docker exec -it nexus3 /bin/bashcat /nexus-data/admin.password</code></pre><p>这地方注意下，cat后不会换行，注意看下密码，用户名是admin，文件中存的就是密码</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112214220341.png" alt="image-20220112214220341" /></p><h2 id="设置密码">设置密码</h2><p>登录后：</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112214637528.png" alt="image-20220112214637528" /></p><p>点击next设置新密码</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112214717867.png" alt="image-20220112214717867" /></p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112214820826.png" alt="image-20220112214820826" /></p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112214831857.png" alt="image-20220112214831857" /></p><h2 id="增加阿里云公共仓库">增加阿里云公共仓库</h2><p>由于默认的里面没有阿里云仓库，用maven的仓库速度慢，所以增加一个阿里云仓库</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112215708898.png" alt="image-20220112215708898" /></p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112215809631.png" alt="image-20220112215809631" /></p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112215951755.png" alt="image-20220112215951755" /></p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112220131151.png" alt="image-20220112220131151" /></p><p>接下来填写信息：name这个随意填，为了方便记忆我填写的aliyun-public-proxy，下面的配置阿里云地址<a href="https://maven.aliyun.com/repository/public，两个填好后点击最下方的Create">https://maven.aliyun.com/repository/public，两个填好后点击最下方的Create</a> repository</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112220511830.png" alt="image-20220112220511830" /></p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112220741095.png" alt="image-20220112220741095" /></p><h2 id="统一私服">统一私服</h2><ul><li>编辑<strong>maven-public</strong></li></ul><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112221401367.png" alt="image-20220112221401367" /></p><ul><li>将刚刚的aliyun-public-proxy放入 <strong>group</strong> 中，并调整优先级，然后保存</li></ul><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112221517343.png" alt="image-20220112221517343" /></p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112221637473.png" alt="image-20220112221637473" /></p><h2 id="查看私服地址">查看私服地址</h2><p>回到上一个页面，点击copy，弹出来的地址就是私服地址</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112221849871.png" alt="image-20220112221849871" /></p><h1 id="使用私服">使用私服</h1><p>注：maven地址：E:\maven\apache-maven-3.6.3</p><h2 id="maven中settingxml-文件配置">maven中setting.xml 文件配置</h2><ul><li><p>下载依赖</p><p>找到mirrors位置，并将其标签内容修改如下</p><pre><code class="language-xml">&lt;mirrors&gt;  &lt;!-- mirror   | Specifies a repository mirror site to use instead of a given repository. The repository that   | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used   | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.   |  &lt;mirror&gt;    &lt;id&gt;mirrorId&lt;/id&gt;    &lt;mirrorOf&gt;repositoryId&lt;/mirrorOf&gt;    &lt;name&gt;Human Readable Name for this Mirror.&lt;/name&gt;    &lt;url&gt;http://my.repository.com/repo/path&lt;/url&gt;  &lt;/mirror&gt;   --&gt;&lt;mirror&gt;    &lt;!--该镜像的唯一标识符。id用来区分不同的mirror元素。 --&gt;    &lt;id&gt;maven-public&lt;/id&gt;    &lt;!--镜像名称 --&gt;    &lt;name&gt;maven-public&lt;/name&gt;    &lt;!--*指的是访问任何仓库都使用我们的私服--&gt;    &lt;mirrorOf&gt;*&lt;/mirrorOf&gt;    &lt;!--该镜像的URL。构建系统会优先考虑使用该URL，而非使用默认的服务器URL。 --&gt;    &lt;url&gt;http://192.168.1.187:8081/repository/maven-public/&lt;/url&gt;       &lt;/mirror&gt;&lt;/mirrors&gt;</code></pre></li><li><p>发布依赖</p><p>找到servers位置，并将其标签内容修改如下</p><pre><code class="language-xml">&lt;servers&gt;  &lt;!-- server   | Specifies the authentication information to use when connecting to a particular server, identified by   | a unique name within the system (referred to by the 'id' attribute below).   |   | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are   |       used together.   |  &lt;server&gt;    &lt;id&gt;deploymentRepo&lt;/id&gt;    &lt;username&gt;repouser&lt;/username&gt;    &lt;password&gt;repopwd&lt;/password&gt;  &lt;/server&gt;  --&gt;  &lt;!-- Another sample, using keys to authenticate.  &lt;server&gt;    &lt;id&gt;siteServer&lt;/id&gt;    &lt;privateKey&gt;/path/to/private/key&lt;/privateKey&gt;    &lt;passphrase&gt;optional; leave empty if not used.&lt;/passphrase&gt;  &lt;/server&gt;  --&gt;  &lt;server&gt;      &lt;id&gt;releases&lt;/id&gt;      &lt;username&gt;admin&lt;/username&gt;      &lt;password&gt;123456&lt;/password&gt;  &lt;/server&gt;  &lt;server&gt;      &lt;id&gt;snapshots&lt;/id&gt;      &lt;username&gt;admin&lt;/username&gt;      &lt;password&gt;123456&lt;/password&gt;  &lt;/server&gt;&lt;/servers&gt;</code></pre></li></ul><h2 id="新建maven项目">新建maven项目</h2><p>我这边建了一个Springboot项目</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112223312451.png" alt="image-20220112223312451" /></p><p>设置maven路径</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112224909515.png" alt="image-20220112224909515" /></p><h2 id="发布依赖">发布依赖</h2><ol><li><p>项目pom中添加 <strong>distributionManagement</strong> 节点</p><pre><code class="language-xml">&lt;distributionManagement&gt;    &lt;repository&gt;        &lt;id&gt;releases&lt;/id&gt;        &lt;name&gt;Releases&lt;/name&gt;        &lt;url&gt;http://192.168.1.187:8081/repository/maven-releases/&lt;/url&gt;    &lt;/repository&gt;    &lt;snapshotRepository&gt;        &lt;id&gt;snapshots&lt;/id&gt;        &lt;name&gt;Snapshot&lt;/name&gt;        &lt;url&gt;http://192.168.1.187:8081/repository/maven-snapshots/&lt;/url&gt;    &lt;/snapshotRepository&gt;&lt;/distributionManagement&gt;</code></pre><p>注：<strong>repository</strong> 里的 <strong>id</strong> 需要和上一步里的 <strong>server id</strong> 名称保持一致。</p></li><li><p>执行 <strong>mvn deploy</strong> 命令发布：</p><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112230213213.png" alt="image-20220112230213213" /></p></li><li><p>查看网页，是否部署成功</p><p>注：</p><ul><li>若项目版本号末尾带有 <strong>-SNAPSHOT</strong>，则会发布到 <strong>snapshots</strong> 快照版本仓库</li><li>若项目版本号末尾带有 <strong>-RELEASES</strong> 或什么都不带，则会发布到 <strong>releases</strong> 正式版本仓库</li></ul><p><img src="https://img.huangge1199.cn/blog/nexusCreate/image-20220112230645860.png" alt="image-20220112230645860" /></p></li></ol>]]>
                    </description>
                    <pubDate>Wed, 12 Jan 2022 19:52:40 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[JPA复合主键使用]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/jpa复合主键使用</link>
                    <description>
                            <![CDATA[<h1 id="1建立带有复合主键的表user">1、建立带有复合主键的表User</h1><p>该表使用 <code>username</code>+<code>phone</code> 做为复合组件</p><pre><code class="language-sql">create table user(    username varchar(50) not null,    phone     varchar(11) not null,    email     varchar(20) default '',    address   varchar(50) default '',    primary key (username, phone)) default charset = utf8</code></pre><h1 id="2java中建立复合主键的实体类">2、java中建立复合主键的实体类</h1><pre><code class="language-java">import lombok.Data;import javax.persistence.*;import java.io.Serializable;@Data@Entitypublic class UserKey implements Serializable {    private String username;    private String phone;}</code></pre><h1 id="3建立表的实体类">3、建立表的实体类</h1><p>在实体类上面使用 @IdClass 注解指定复合主键。同时，需要在 name 和 phone 字段上面使用 @Id 注解标记为主键</p><pre><code class="language-java">import lombok.Data;import javax.persistence.*;@Data@Entity@Table(name = &quot;user&quot;)@IdClass(value = UserKey.class)public class User {    @Id    @Column(nullable = false)    private String username;    @Id    @Column(nullable = false)    private String phone;    @Column    private String email;    @Column    private String address;}</code></pre>]]>
                    </description>
                    <pubDate>Wed, 05 Jan 2022 15:14:53 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[python3学习笔记--条件控制用法整理]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/python3学习笔记--条件控制用法整理</link>
                    <description>
                            <![CDATA[<h1 id="if">if</h1><pre><code class="language-python">if_stmt ::=  &quot;if&quot; assignment_expression &quot;:&quot; suite             (&quot;elif&quot; assignment_expression &quot;:&quot; suite)*             [&quot;else&quot; &quot;:&quot; suite]</code></pre><p>用法：</p><pre><code class="language-python">if EXPRESSION1:    SUITE1elif EXPRESSION2:    SUITE2else:    SUITE</code></pre><p>常用的操作符：</p><ul><li>&quot;&lt;&quot;：小于</li><li>&quot;&lt;=&quot;：小于等于</li><li>&quot;&gt;&quot;：大于</li><li>&quot;&gt;=&quot;：大于等于</li><li>&quot;==&quot;：等于</li><li>&quot;!=&quot;：不等于</li><li>&quot;and&quot;：并且</li><li>&quot;or&quot;：或者</li></ul><h1 id="with">with</h1><pre><code class="language-python">with_stmt          ::=  &quot;with&quot; ( &quot;(&quot; with_stmt_contents &quot;,&quot;? &quot;)&quot; | with_stmt_contents ) &quot;:&quot; suitewith_stmt_contents ::=  with_item (&quot;,&quot; with_item)*with_item          ::=  expression [&quot;as&quot; target]</code></pre><p>用法：</p><pre><code class="language-python">with EXPRESSION as TARGET:    SUITE或者with A() as a, B() as b:    SUITE或者with A() as a:    with B() as b:        SUITE或者with (    A() as a,    B() as b,):    SUITE</code></pre><h1 id="match310新特性">match（3.10新特性）</h1><pre><code class="language-python">match_stmt   ::=  'match' subject_expr &quot;:&quot; NEWLINE INDENT case_block+ DEDENTsubject_expr ::=  star_named_expression &quot;,&quot; star_named_expressions?                  | named_expressioncase_block   ::=  'case' patterns [guard] &quot;:&quot; block</code></pre><p>用法：</p><pre><code class="language-python">match variable: #这里的variable是需要判断的内容    case [&quot;quit&quot;]:         statement_block_1 # 对应案例的执行代码，当variable=&quot;quit&quot;时执行statement_block_1    case [&quot;go&quot;, direction]:         statement_block_2    case [&quot;drop&quot;, *objects]:         statement_block_3    ... # 其他的case语句    case _: #如果上面的case语句没有命中，则执行这个代码块，类似于Switch的default        statement_block_default</code></pre>]]>
                    </description>
                    <pubDate>Wed, 29 Dec 2021 16:04:06 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[python3学习笔记--两种排序方法]]>
                    </title>
                    <link>https://halo.huangge1199.cn/archives/python3学习笔记--两种排序方法</link>
                    <description>
                            <![CDATA[<h1 id="列表排序方法">列表排序方法</h1><ul><li>sort()：仅对list对象进行排序，会改变list自身的顺序，没有返回值，即<strong>原地排序</strong></li><li>sorted()：对所有可迭代对象进行排序，返回排序后的新对象，原对象保持不变；</li></ul><h1 id="sort">sort()</h1><p>list.sort(key=None, reverse=False)</p><ul><li><strong>key</strong>：设置排序方法，或指定list中用于排序的元素；</li><li><strong>reverse</strong>：升降序排列，默认为升序排列；</li></ul><p>例子：</p><pre><code class="language-python">nums = [2, 3, 5, 1, 6]nums.sort()print(nums)  # [1, 2, 3, 5, 6]nums.sort(key=None, reverse=True)print(nums)  # [6, 5, 3, 2, 1]    students = [('john', 'C', 15), ('jane', 'A', 12), ('dave', 'B', 10)]students.sort(key=lambda x: x[2])  # 按照列表中第三个元素排序print(students)  # [('dave', 'B', 10), ('jane', 'A', 12), ('john', 'C', 15)]</code></pre><h1 id="sorted">sorted()</h1><p>sorted(iterable [, key[, reverse]])</p><ul><li><strong>key</strong> ：设置排序方法，或指定迭代对象中，用于排序的元素；</li><li><strong>reverse</strong> ：升降序排列，默认为升序排列；</li></ul><p>例子：</p><pre><code class="language-python">nums = [2, 3, 5, 1, 6]newNums = sorted(nums)print(nums)  # [2, 3, 5, 1, 6]print(newNums)  # [1, 2, 3, 5, 6]students = [('john', 'C', 15), ('jane', 'A', 12), ('dave', 'B', 10)]newStudents = sorted(students, key=lambda x: x[1])print(students)  # [('john', 'C', 15), ('jane', 'A', 12), ('dave', 'B', 10)]print(newStudents)  # [('jane', 'A', 12), ('dave', 'B', 10), ('john', 'C', 15)]</code></pre><script src="https://readmore.openwrite.cn/js/readmore.js" type="text/javascript"></script><script>    const btw = new BTWPlugin();    btw.init({        id: 'article-container',        blogId: '28702-1640918923546-365',        name: '龙儿之家',        qrcode: 'https://img.huangge1199.cn/blog/images/WeaselLong.jpg',        keyword: 'more',    });</script>]]>
                    </description>
                    <pubDate>Wed, 29 Dec 2021 10:20:17 CST</pubDate>
                </item>
    </channel>
</rss>