<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh_CN">
  <title>Pardofelis</title>
  <subtitle>No description</subtitle>
  <link href="https://www.pardofelis.top/" rel="alternate" type="text/html"/>
  <link href="https://www.pardofelis.top/atom.xml" rel="self" type="application/atom+xml"/>
  <id>https://www.pardofelis.top/</id>
  <updated>2026-10-14T00:00:00.000Z</updated>
  <entry>
    <title>Blender 角色动画学习计划（10 周）</title>
    <link href="https://www.pardofelis.top/posts/19fd21ca/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/19fd21ca/</id>
    <published>2026-10-14T00:00:00.000Z</published>
    <updated>2026-10-14T00:00:00.000Z</updated>
    <summary>每天约一小时，从 Blender 基础、PMX 导入到角色动画与 GLB 导出的 70 天实践安排。</summary>
    <content type="html"><![CDATA[<p>这份计划以现有的 PMX 角色模型为练习素材，目标是制作一个可用于 3D 网页菜单的角色资源。每天安排约 1 小时：前 20 分钟看教程，中间 30 分钟跟着操作，最后 10 分钟独立重复、记录问题并保存工程。完成某一周的验收后再进入下一阶段；实际进度可以随模型已有的骨骼和表情调整。</p>
<h2>第 1 周：认识 Blender</h2>
<ol>
<li><strong>Day 1</strong>：认识界面、主要编辑器和工作区。</li>
<li><strong>Day 2</strong>：练习旋转、缩放和平移视图。</li>
<li><strong>Day 3</strong>：选择、隐藏和删除物体。</li>
<li><strong>Day 4</strong>：使用 <code>G</code> 键和坐标轴移动物体。</li>
<li><strong>Day 5</strong>：使用 <code>R</code> 键旋转物体。</li>
<li><strong>Day 6</strong>：使用 <code>S</code> 键缩放物体。</li>
<li><strong>Day 7</strong>：制作简单桌面场景，重复上述操作。</li>
</ol>
<p><strong>验收</strong>：不看教程，能够创建、移动、旋转、缩放和删除物体。</p>
<h2>第 2 周：模型基础</h2>
<ol>
<li><strong>Day 8</strong>：切换对象模式与编辑模式。</li>
<li><strong>Day 9</strong>：认识顶点、边、面并练习选择。</li>
<li><strong>Day 10</strong>：编辑网格，简单修改模型形状。</li>
<li><strong>Day 11</strong>：了解细分等修改器。</li>
<li><strong>Day 12</strong>：创建材质并为模型设置颜色。</li>
<li><strong>Day 13</strong>：认识纹理及 PNG 贴图的作用。</li>
<li><strong>Day 14</strong>：创建并保存一个带材质的模型。</li>
</ol>
<p><strong>验收</strong>：理解模型、材质和贴图之间的关系。</p>
<h2>第 3 周：导入自己的 PMX 模型</h2>
<ol>
<li><strong>Day 15</strong>：认识 PMX 文件、贴图文件和骨骼结构。</li>
<li><strong>Day 16</strong>：按当前 Blender 版本安装兼容的 MMD Tools。</li>
<li><strong>Day 17</strong>：导入自己的 PMX 角色模型。</li>
<li><strong>Day 18</strong>：检查角色贴图是否正确显示。</li>
<li><strong>Day 19</strong>：认识 Mesh 和 Armature。</li>
<li><strong>Day 20</strong>：找到头部、手臂等骨骼。</li>
<li><strong>Day 21</strong>：保存完整的角色工程，并记录缺失或异常的资源。</li>
</ol>
<p><strong>验收</strong>：角色在 Blender 中正常显示，能够查看骨骼结构。如果导入失败，先排查插件版本、贴图路径和模型文件，再继续后续课程。</p>
<h2>第 4 周：动画基础</h2>
<ol>
<li><strong>Day 22</strong>：认识 Timeline。</li>
<li><strong>Day 23</strong>：用关键帧制作简单移动动画。</li>
<li><strong>Day 24</strong>：制作旋转动画。</li>
<li><strong>Day 25</strong>：比较不同的动画插值方式。</li>
<li><strong>Day 26</strong>：使用 Dope Sheet 编辑关键帧。</li>
<li><strong>Day 27</strong>：认识 Shape Keys。</li>
<li><strong>Day 28</strong>：制作并保存一个简单循环动画。</li>
</ol>
<p><strong>验收</strong>：独立制作一个简单的关键帧动画。</p>
<h2>第 5 周：角色骨骼</h2>
<ol>
<li><strong>Day 29</strong>：理解 Armature 骨骼系统。</li>
<li><strong>Day 30</strong>：使用 Pose Mode 调整角色姿势。</li>
<li><strong>Day 31</strong>：控制头部骨骼，让角色转头。</li>
<li><strong>Day 32</strong>：调整颈部骨骼，做出自然歪头。</li>
<li><strong>Day 33</strong>：控制上半身骨骼。</li>
<li><strong>Day 34</strong>：控制手臂，制作挥手姿势。</li>
<li><strong>Day 35</strong>：保存三种可复用的角色姿势。</li>
</ol>
<p><strong>验收</strong>：角色能够转头、歪头和抬手。</p>
<h2>第 6 周：角色面部表情</h2>
<ol>
<li><strong>Day 36</strong>：检查 PMX 模型现有的面部 Morph。</li>
<li><strong>Day 37</strong>：寻找眼球骨骼或其他眼睛控制方式。</li>
<li><strong>Day 38</strong>：制作眨眼动作。</li>
<li><strong>Day 39</strong>：制作微笑表情。</li>
<li><strong>Day 40</strong>：制作惊讶表情。</li>
<li><strong>Day 41</strong>：调整表情之间的过渡。</li>
<li><strong>Day 42</strong>：保存三种表情动画。</li>
</ol>
<p><strong>验收</strong>：角色能够眨眼、微笑和表现惊讶。若模型缺少对应 Morph，先检查已有控制器；必要时调整这一周的目标。</p>
<h2>第 7 周：角色待机动画</h2>
<ol>
<li><strong>Day 43</strong>：观察角色自然站立时的姿势与重心。</li>
<li><strong>Day 44</strong>：制作轻微呼吸动作。</li>
<li><strong>Day 45</strong>：增加左右重心变化。</li>
<li><strong>Day 46</strong>：加入小幅头部摆动。</li>
<li><strong>Day 47</strong>：增加自然的手臂运动。</li>
<li><strong>Day 48</strong>：处理循环动画的首尾关键帧。</li>
<li><strong>Day 49</strong>：完成第一版待机动画。</li>
</ol>
<p><strong>验收</strong>：角色能够自然循环待机约 8～12 秒。</p>
<h2>第 8 周：动画优化</h2>
<ol>
<li><strong>Day 50</strong>：认识 Graph Editor 动画曲线。</li>
<li><strong>Day 51</strong>：用缓入缓出优化头部动作。</li>
<li><strong>Day 52</strong>：调整动作速度和节奏。</li>
<li><strong>Day 53</strong>：避免机械式眨眼。</li>
<li><strong>Day 54</strong>：协调头部和身体动作。</li>
<li><strong>Day 55</strong>：修复循环边界的跳变。</li>
<li><strong>Day 56</strong>：完成第二版待机动画。</li>
</ol>
<p><strong>验收</strong>：动作自然，没有明显的突然停止或跳变。</p>
<h2>第 9 周：角色交互动画</h2>
<ol>
<li><strong>Day 57</strong>：制作疑惑时的歪头动作。</li>
<li><strong>Day 58</strong>：制作挥手动作。</li>
<li><strong>Day 59</strong>：组合骨骼和表情，制作惊讶反馈。</li>
<li><strong>Day 60</strong>：制作开心反馈。</li>
<li><strong>Day 61</strong>：用 Action Editor 管理动作。</li>
<li><strong>Day 62</strong>：整理并命名每个动画。</li>
<li><strong>Day 63</strong>：完成可复用的角色动作库。</li>
</ol>
<p><strong>验收</strong>：拥有待机、歪头、挥手、惊讶等可复用动作。</p>
<h2>第 10 周：网页模型导出</h2>
<ol>
<li><strong>Day 64</strong>：了解 glTF / GLB 格式。</li>
<li><strong>Day 65</strong>：检查模型缩放与朝向。</li>
<li><strong>Day 66</strong>：检查材质和贴图引用。</li>
<li><strong>Day 67</strong>：检查需要导出的 Action。</li>
<li><strong>Day 68</strong>：导出 GLB 角色资源。</li>
<li><strong>Day 69</strong>：在网页模型查看器或开发环境中检查骨骼、表情和动画。</li>
<li><strong>Day 70</strong>：整理 Blender 工程、贴图和最终 GLB。</li>
</ol>
<p><strong>验收</strong>：GLB 能被 Three.js 读取，并保留项目需要的骨骼、表情与动画。MMD 的特殊材质和物理效果未必能完整转换，应逐项检查并记录后续网页端需要补做的效果。</p>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Study"/>
  </entry>
  <entry>
    <title>MySQL 数据库备份与恢复</title>
    <link href="https://www.pardofelis.top/posts/250b44de/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/250b44de/</id>
    <published>2026-10-13T00:00:00.000Z</published>
    <updated>2026-10-13T00:00:00.000Z</updated>
    <summary>从备份策略、mysqldump 实操到二进制日志时间点恢复，系统整理 MySQL 备份与恢复流程。</summary>
    <content type="html"><![CDATA[<p>备份的目标是<strong>在约定时间内，把数据恢复到约定的时间点</strong>。本文以 MySQL 8.4 和以 InnoDB 为主的数据库为例，介绍备份选择、逻辑备份、二进制日志归档、时间点恢复和恢复演练。示例中的 <code>appdb</code>、账户、路径和日志位置均需按实际环境替换；恢复命令应先在隔离实例演练。</p>
<h2>一、先确定恢复目标</h2>
<ul>
<li><strong>RPO（恢复点目标）</strong>：最多允许丢失多长时间的数据。例如 RPO 为 15 分钟，就需要保证备份和日志归档能把数据恢复到故障前约 15 分钟以内。</li>
<li><strong>RTO（恢复时间目标）</strong>：从故障发生到服务恢复可用，最多允许多久。数据量大时，逻辑备份的导入速度往往比导出速度更需要关注。</li>
<li><strong>恢复范围</strong>：是误删一张表、损坏一个库，还是整台实例不可用？单库 SQL 文件适合按库恢复；实例级故障还需要配置、账号、证书、插件、二进制日志等信息。</li>
<li><strong>保留期</strong>：至少覆盖发现错误所需的时间。全量备份和二进制日志必须形成连续链，不能只保留最近一个 SQL 文件。</li>
</ul>
<p>先写下 RPO、RTO 和恢复范围，再决定备份频率与工具。复制一份文件只是备份动作；只有恢复演练成功，才能证明方案可用。</p>
<h2>二、几种备份方式怎么选</h2>
<table>
<thead>
<tr>
<th>方式</th>
<th>保存的内容</th>
<th>适用场景</th>
<th>主要限制</th>
</tr>
</thead>
<tbody>
<tr>
<td>逻辑备份</td>
<td>建库建表语句、数据、视图和可选的存储对象</td>
<td>小中型数据库、按库迁移、检查或选择性恢复</td>
<td>大库导出和重建索引可能很慢；不含服务器配置与二进制日志</td>
</tr>
<tr>
<td>物理备份</td>
<td>数据文件及工具要求的相关文件</td>
<td>大库、对恢复速度要求较高的实例</td>
<td>依赖工具、存储引擎和版本兼容性；不能直接复制运行中的 InnoDB 数据目录充当一致备份</td>
</tr>
<tr>
<td>二进制日志归档</td>
<td>全量备份后的变更事件</td>
<td>把全量备份推进到指定时间点</td>
<td>单独的日志通常不能替代基线全量备份，日志缺一段就无法连续回放</td>
</tr>
<tr>
<td>文件系统快照</td>
<td>某一时刻的文件系统状态</td>
<td>配合数据库一致性措施做快速快照</td>
<td>普通快照不自动保证 MySQL 内部一致性，也不能代替异地副本</td>
</tr>
</tbody>
</table>
<p><strong>全量备份</strong>保存一个完整基线；<strong>增量备份</strong>只保存相对上次备份的变化；<strong>差异备份</strong>保存相对上次全量备份的变化。物理备份工具可支持页级增量；MySQL 二进制日志则可记录基线之后的变更，用于时间点恢复。两者不是同一种文件格式，恢复步骤也不同。</p>
<p>一个可落地的例子是：每天一次全量备份，连续归档二进制日志，保留至少两轮完整备份链，并把副本放在另一台设备或对象存储中。具体间隔要由 RPO、RTO、数据量和恢复演练结果决定。备份文件应加密，密钥与备份分开保管；限制读取权限，避免把数据库密码写在命令行或备份脚本中。</p>
<h2>三、备份前检查</h2>
<p>先确认版本、存储引擎、二进制日志和保留期：</p>
<pre><code>SELECT VERSION();
SELECT @@GLOBAL.log_bin, @@GLOBAL.gtid_mode, @@GLOBAL.binlog_expire_logs_seconds;
SELECT TABLE_NAME, ENGINE
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'appdb' AND TABLE_TYPE = 'BASE TABLE';
SHOW BINARY LOGS;
</code></pre>
<p><code>log_bin</code> 为 <code>ON</code> 才能按下文做二进制日志时间点恢复。MySQL 8.4 默认启用二进制日志，但仍应检查实际实例配置。<code>binlog_expire_logs_seconds</code> 必须长于需要保留的恢复窗口，并留出备份、传输及故障发现的余量；如果日志在归档前被清理，就会出现恢复缺口。</p>
<p>检查是否存在 MyISAM、MEMORY 等非事务表。<code>mysqldump --single-transaction</code> 只保证 InnoDB 等事务表在同一一致性快照中导出；它<strong>不能</strong>让非事务表自动变成一致快照。备份期间也不要对待导出的表执行 <code>ALTER TABLE</code>、<code>CREATE TABLE</code>、<code>DROP TABLE</code>、<code>RENAME TABLE</code> 或 <code>TRUNCATE TABLE</code>，否则导出可能失败或内容不正确。</p>
<p>备份账户需要与导出对象和选项相符的权限。例如表需要 <code>SELECT</code>，视图需要 <code>SHOW VIEW</code>，触发器需要 <code>TRIGGER</code>，事件需要 <code>EVENT</code>；<code>--routines</code> 和记录日志坐标的 <code>--source-data</code> 还涉及额外权限。不要为了省事长期使用管理员账户执行定时备份。</p>
<h2>四、用 mysqldump 做单库逻辑备份</h2>
<p>下面的 PowerShell 示例导出 <code>appdb</code>，并把 SQL 写到 <code>D:/mysql-backup/appdb-full.sql</code>。先确保备份目录存在：</p>
<pre><code>New-Item -ItemType Directory -Force -Path 'D:/mysql-backup'
mysqldump --host=127.0.0.1 --user=backup_user --password --databases appdb --single-transaction --quick --routines --events --triggers --hex-blob --no-tablespaces --default-character-set=utf8mb4 --result-file='D:/mysql-backup/appdb-full.sql'
if ($LASTEXITCODE -ne 0) { throw 'mysqldump failed' }
Get-FileHash -Algorithm SHA256 'D:/mysql-backup/appdb-full.sql'
</code></pre>
<p><code>--password</code> 不附带密码值时会交互式提示输入。<code>--databases appdb</code> 会在文件中包含建库与 <code>USE</code> 语句；<code>--routines</code>、<code>--events</code>、<code>--triggers</code> 分别覆盖存储程序、事件和触发器；<code>--hex-blob</code> 使二进制列以十六进制形式导出；<code>--no-tablespaces</code> 省略表空间 DDL。<code>--quick</code> 按行从服务器读取，适合较大表。正式脚本还应记录开始与结束时间、退出码、文件大小和校验值，并在失败时通知管理员。</p>
<p>Windows PowerShell 的 <code>&gt;</code> 重定向可能把转储写成 UTF-16，导致 MySQL 无法正确导入，因此这里使用 <code>--result-file</code>。此选项会覆盖同名文件，即使导出途中报错也可能留下文件；<strong>文件存在或哈希算得出来，并不等于备份成功</strong>。应同时检查退出码并定期做完整恢复演练。</p>
<blockquote>
<p><strong>GTID 注意</strong>：<code>mysqldump</code> 默认的 <code>--set-gtid-purged=AUTO</code> 在启用 GTID 时可能把源实例<strong>所有</strong>已执行事务的 GTID 写入导出文件，即使只导出了 <code>appdb</code>。向已有实例导入局部备份、搭建复制或连续导入多个局部备份前，必须检查该语句和目标实例的 GTID 状态。<code>--set-gtid-purged=OFF</code> 适用于明确不需要保留该 GTID 集的独立数据副本，不能机械地用于所有恢复场景。</p>
</blockquote>
<h3>恢复单库 SQL 文件</h3>
<p>先在隔离的空实例测试。下例的转储包含 <code>CREATE DATABASE</code> 和 <code>USE</code>，因此命令不必额外指定数据库：</p>
<pre><code>mysql --host=127.0.0.1 --user=restore_user --password --execute='SOURCE D:/mysql-backup/appdb-full.sql'
if ($LASTEXITCODE -ne 0) { throw 'restore failed' }
</code></pre>
<p>不要在生产实例上直接试运行：转储可能包含 <code>DROP TABLE</code>、<code>CREATE TABLE</code> 和大量 <code>INSERT</code>。导入后至少检查表、关键记录数、视图、存储程序、触发器和事件，再运行应用的关键读写流程。可以先用以下查询查看对象是否恢复：</p>
<pre><code>SHOW FULL TABLES FROM appdb;
SELECT ROUTINE_NAME, ROUTINE_TYPE
FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = 'appdb';
SELECT TRIGGER_NAME
FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = 'appdb';
SELECT EVENT_NAME
FROM information_schema.EVENTS WHERE EVENT_SCHEMA = 'appdb';
</code></pre>
<p>记录原库与恢复库的关键业务表行数、抽样记录和应用校验结果。行数一致不能证明全部内容正确，但能发现明显漏表或漏数据。</p>
<h2>五、全量备份加二进制日志：时间点恢复</h2>
<p>误删数据往往在下一次全量备份前才被发现。要恢复到误操作<strong>之前</strong>，需要一份一致的基线、基线对应的二进制日志坐标，以及从该坐标到目标时间点之间<strong>连续且未损坏</strong>的日志文件。</p>
<p>以下示例假设源实例只承载 <code>appdb</code>，恢复目标是隔离的空实例。多库实例应设计实例级一致备份和全实例日志回放，不能把单库转储与全实例日志直接拼接后当作通用恢复方案。</p>
<h3>1. 记录与备份同一时刻的日志坐标</h3>
<p>在上面的 <code>mysqldump</code> 命令中额外加入 <code>--source-data=2</code>，并另存为一份用于时间点恢复的基线：</p>
<pre><code>mysqldump --host=127.0.0.1 --user=backup_user --password --databases appdb --single-transaction --quick --routines --events --triggers --hex-blob --no-tablespaces --source-data=2 --result-file='D:/mysql-backup/appdb-pitr-base.sql'
if ($LASTEXITCODE -ne 0) { throw 'baseline dump failed' }
</code></pre>
<p>文件内会有一条被注释掉的 <code>CHANGE REPLICATION SOURCE TO</code> 语句，记录类似 <code>SOURCE_LOG_FILE='binlog.000123', SOURCE_LOG_POS=154</code> 的坐标。<code>=2</code> 让它只作记录，不会在导入时真的执行复制配置。该选项要求二进制日志已开启和相应权限；它在开始导出时可能短暂获取全局读锁。保持备份期间没有上述 DDL。</p>
<h3>2. 保存完整的日志链</h3>
<p><code>SHOW BINARY LOGS</code> 列出当前日志文件。将基线坐标所在的文件以及之后所有需要的文件安全地归档到另一处，持续监控归档是否中断。MySQL 也支持用 <code>mysqlbinlog --read-from-remote-server --raw --stop-never</code> 连续拉取原始二进制日志；该进程断线后不会自动重连，必须由服务管理和监控负责重启与检查缺口。</p>
<p>不要在确认归档、复制从库需求和恢复窗口之前清理二进制日志。全量 SQL 文件、二进制日志、版本与配置记录应一起管理；仅有 SQL 文件无法恢复其导出之后的事务。</p>
<h3>3. 在隔离实例回放到正确位置</h3>
<p>先导入 <code>appdb-pitr-base.sql</code>。然后用 <code>mysqlbinlog</code> 检查日志，找出误操作事务<strong>开始前</strong>的事件边界，确认目标日志文件及位置。相比只按墙上时钟截断，按文件和事件位置更明确；不要把停止位置设在一个事务的中间。</p>
<p>假设基线坐标为 <code>binlog.000123:154</code>，误操作从 <code>binlog.000124:8876</code> 开始，可在 PowerShell 中先生成回放文件，再用同一个 MySQL 客户端连接导入：</p>
<pre><code>mysqlbinlog --start-position=154 --stop-position=8876 --result-file='D:/mysql-backup/replay.sql' 'D:/mysql-backup/binlog.000123' 'D:/mysql-backup/binlog.000124'
if ($LASTEXITCODE -ne 0) { throw 'mysqlbinlog failed' }
mysql --binary-mode --host=127.0.0.1 --user=restore_user --password --execute='SOURCE D:/mysql-backup/replay.sql'
if ($LASTEXITCODE -ne 0) { throw 'binlog replay failed' }
</code></pre>
<p><code>--start-position</code> 作用于列出的<strong>第一个</strong>日志文件，<code>--stop-position</code> 作用于<strong>最后一个</strong>。中间日志按顺序完整处理。一次生成回放文件并通过一次客户端连接导入，能避免跨日志文件的会话状态被拆断。这里使用 <code>mysqlbinlog --result-file</code>，避免 PowerShell 文本重定向改变文件编码。回放账户还需要执行日志事件及内部 <code>BINLOG</code> 语句所需的权限。</p>
<p>最后核对目标事务之前的数据、对象定义和应用行为。确认结果后，再按业务切换流程恢复服务；不要在未经验证的情况下覆盖原始实例。</p>
<h2>六、物理备份和大库的选择</h2>
<p>当 SQL 导入时间达不到 RTO 时，考虑物理备份。MySQL Enterprise Backup 的 <code>mysqlbackup</code> 支持 InnoDB 在线物理备份与增量备份；MySQL Shell 的 dump/load 工具则提供并行的<strong>逻辑</strong>导出与导入。选择前应测量真实数据量下的备份时间、恢复时间、磁盘占用和对业务的影响。</p>
<p>直接压缩或复制<strong>正在运行</strong>的 InnoDB 数据目录，不能代替了解事务与日志的一致备份工具。停机文件备份需要在服务器完全停止后保存完整数据目录及恢复所需的配置与日志，并在兼容的 MySQL 环境中演练。不要只复制某个 <code>.ibd</code> 文件就认为已经备份了整张表。</p>
<h2>七、自动化和恢复演练清单</h2>
<ol>
<li>每次备份记录版本、实例标识、范围、开始和结束时间、退出码、文件大小、哈希、基线日志坐标，以及备份文件存放位置。</li>
<li>自动检查最近一次成功备份的时间、日志归档连续性、剩余空间和保留期；失败要主动告警。备份文件至少保留一份与数据库主机分离的副本。</li>
<li>定期在隔离实例从备份<strong>完整恢复</strong>，再回放日志到选定时间点，测量实际 RTO，并检查关键业务数据和应用启动。恢复演练不能只做 <code>SQL</code> 文件语法检查。</li>
<li>轮换与限制备份账户；交互输入密码或使用受保护的客户端配置，避免把明文密码写入命令行、脚本、日志和仓库。控制备份文件及解密密钥的访问权限。</li>
<li>清理旧备份时，以整条可恢复链为单位：保留对应全量备份和从其坐标开始的全部必要日志。先确认副本与恢复演练，再清理过期文件。</li>
</ol>
<h3>常见误区</h3>
<table>
<thead>
<tr>
<th>做法</th>
<th>问题</th>
</tr>
</thead>
<tbody>
<tr>
<td>只复制正在运行的 <code>.ibd</code> 或数据目录</td>
<td>文件之间可能不是同一一致状态，恢复还依赖数据字典、日志和其他文件</td>
</tr>
<tr>
<td>只有全量 SQL 文件，没有后续二进制日志</td>
<td>最多只能回到导出时刻</td>
</tr>
<tr>
<td>使用 <code>--single-transaction</code> 就认为所有表都一致</td>
<td>非事务表不受该快照保护；备份期间的 DDL 也会破坏结果</td>
</tr>
<tr>
<td>只看备份文件存在或哈希正常</td>
<td>不能证明 SQL 可导入、对象齐全或应用可运行</td>
</tr>
<tr>
<td>PowerShell 中用 <code>&gt;</code> 写 <code>mysqldump</code> 输出</td>
<td>可能得到 UTF-16 文件，导入失败</td>
</tr>
<tr>
<td>日志自动过期却没有归档监控</td>
<td>恢复链可能已经断裂</td>
</tr>
<tr>
<td>把单库备份的 GTID 语句直接导入已有复制拓扑</td>
<td>可能污染或冲突目标实例的 GTID 集</td>
</tr>
</tbody>
</table>
<h2>参考资料</h2>
<ul>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/backup-types.html">MySQL 8.4 手册：备份类型</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/mysqldump.html">MySQL 8.4 手册：mysqldump 选项与限制</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/backup-methods.html">MySQL 8.4 手册：数据库备份方法</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/point-in-time-recovery-binlog.html">MySQL 8.4 手册：二进制日志时间点恢复</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/mysqlbinlog.html">MySQL 8.4 手册：mysqlbinlog</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/mysqlbinlog-backup.html">MySQL 8.4 手册：使用 mysqlbinlog 备份日志</a></li>
</ul>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Study"/>
  </entry>
  <entry>
    <title>FineBI 基础入门：从数据到仪表板</title>
    <link href="https://www.pardofelis.top/posts/21b9e180/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/21b9e180/</id>
    <published>2026-10-12T00:00:00.000Z</published>
    <updated>2026-10-12T00:00:00.000Z</updated>
    <summary>认识 FineBI 的分析主题、维度与指标，并用一份订单数据完成第一张仪表板。</summary>
    <content type="html"><![CDATA[<h1>FineBI 基础入门：从数据到仪表板</h1>
<p>FineBI 是帆软推出的商业智能（BI）工具。它把数据处理、可视化分析和分享放在同一套工作流程里。对于第一次使用的人，先记住一条主线：<strong>明确问题 → 准备数据 → 制作组件 → 组合仪表板 → 分享结论</strong>。本文以 FineBI 7.x 的官方帮助文档为参考；使用其他版本时，界面名称和按钮位置可能有所不同。</p>
<h2>先弄清几个概念</h2>
<table>
<thead>
<tr>
<th>概念</th>
<th>可以怎样理解</th>
<th>订单分析示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>数据源</td>
<td>提供原始记录的文件或数据库</td>
<td>Excel 订单表</td>
</tr>
<tr>
<td>分析主题</td>
<td>放置数据处理结果、图表和仪表板的工作空间</td>
<td>「九月订单分析」</td>
</tr>
<tr>
<td>维度</td>
<td>用来分组或筛选的字段</td>
<td>日期、地区、商品</td>
</tr>
<tr>
<td>指标</td>
<td>要计算和比较的数值</td>
<td>销售额、订单数</td>
</tr>
<tr>
<td>组件</td>
<td>一张表格或图表</td>
<td>各地区销售额柱形图</td>
</tr>
<tr>
<td>仪表板</td>
<td>把多个组件组织在一起的分析页面</td>
<td>销售总览</td>
</tr>
</tbody>
</table>
<p>例如，“哪个地区卖得最多？”中的<strong>地区</strong>是维度，<strong>销售额</strong>是指标。把销售额按地区汇总，才能得到可比较的结果。这里的“销售额”是本文示例字段的业务名称；实际项目应先确认退款、折扣、税费等是否已经计入。</p>
<h2>准备一份可练习的数据</h2>
<p>可以把下面的内容保存为 <code>orders.csv</code>，用它练习导入和制图。每行代表一笔订单，金额单位为元。</p>
<pre><code>order_id,order_date,region,product,sales_amount
O001,2026-09-01,华东,键盘,120
O002,2026-09-01,华北,鼠标,200
O003,2026-09-02,华东,鼠标,80
O004,2026-09-02,华北,键盘,90
O005,2026-09-03,华东,耳机,150
O006,2026-09-03,华北,耳机,110
</code></pre>
<p>上传前检查三个地方：<code>order_date</code> 应识别为日期，<code>sales_amount</code> 应识别为数值，<code>order_id</code> 不应重复。若一笔订单可能占多行，直接统计行数就不等于订单数，需要按订单编号去重计数。示例中每行恰好是一笔订单，因此行数为 6。</p>
<h2>完成第一次分析</h2>
<ol>
<li>进入「我的分析」，在「全部分析」中新建一个分析主题，例如「九月订单分析」。</li>
<li>在主题中添加本地 Excel 文件或 CSV 文件。FineBI 也支持由管理员准备企业数据库中的数据；首次练习用本地文件更容易看清分析过程。</li>
<li>检查字段类型和数据内容。确认日期、地区、金额没有导入错误，再开始制作组件。</li>
<li>创建一个柱形图：用「地区」分组，用「销售额」做<strong>求和</strong>，比较各地区销售额。</li>
<li>再创建一个按「订单日期」分组的趋势图，仍对「销售额」求和，观察每天的变化。</li>
<li>新建仪表板，把两个组件放进去，写上标题，并标明数据时间范围及金额单位。保存后根据需要预览、发布或分享。</li>
</ol>
<p>这份示例数据的销售额合计应为 <strong>750 元</strong>；华东为 <strong>350 元</strong>，华北为 <strong>400 元</strong>。按日汇总分别是 9 月 1 日 <strong>320 元</strong>、2 日 <strong>170 元</strong>、3 日 <strong>260 元</strong>。如果图表与这些数字不符，先检查金额字段是否被识别为文本、汇总方式是否选成了计数，以及是否设置了过滤条件。</p>
<p>:::tip[先确定问题，再选图表]
比较地区销售额，可以用柱形图；观察日期变化，可以用折线图。图表的作用是让读者更快看到差异和趋势，不必为了丰富仪表板而堆放很多图。
:::</p>
<h2>看懂结果，比做出图更重要</h2>
<p>从示例可以说：“这 6 笔订单中，华北销售额比华东高 50 元。”但不能据此断言“华北市场一直更好”：数据只有三天，也没有纳入订单成本、退货和更长时间的变化。写分析结论时，应同时交代<strong>指标口径、时间范围和数据范围</strong>。</p>
<p>分享仪表板前，还要确认查看者具有相应权限。企业数据通常由管理员维护连接与数据权限，业务人员再在获授权的数据范围内分析。若页面上缺少添加数据、发布等入口，可先检查账号权限和所用版本的操作说明。</p>
<h2>下一步学什么</h2>
<p>完成第一张仪表板后，可以按这个顺序继续练习：先掌握过滤与排序，再学习计算字段和分组汇总，最后尝试多个组件之间的联动与分享。每增加一步，都用一两个已知结果核对计算是否符合预期。</p>
<h2>参考资料</h2>
<ul>
<li><a href="https://help.fanruan.com/finebi/doc-view-818.html">FineBI 官方：5 分钟上手 FineBI</a></li>
<li><a href="https://help.fanruan.com/finebi/doc-view-2769.html">FineBI 官方：分析主题简介</a></li>
<li><a href="https://help.fanruan.com/finebi/doc-view-1653.html">FineBI 官方：制作第一个组件</a></li>
<li><a href="https://help.fanruan.com/finebi/doc-view-1854.html">FineBI 官方：分享协作概述</a></li>
</ul>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Study"/>
  </entry>
  <entry>
    <title>Python 基础笔记：从语法到实用脚本</title>
    <link href="https://www.pardofelis.top/posts/02b8403c/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/02b8403c/</id>
    <published>2026-10-12T00:00:00.000Z</published>
    <updated>2026-10-12T00:00:00.000Z</updated>
    <summary>一份面向已有少量基础的 Python 笔记：速查类型、容器、控制流、函数和异常，再用 CSV 统计脚本串起文件处理与测试。</summary>
    <content type="html"><![CDATA[<h1>Python 基础笔记：从语法到实用脚本</h1>
<p>这份笔记适合已经写过几行 Python、想把零散语法串成一个可用脚本的人。前半部分是可以回查的语法要点与易错点，后半部分完成一个“读取 CSV 并统计学习时间”的命令行脚本。示例使用 Python 3.10 及以上版本，仅依赖标准库。</p>
<h2>1. 值、类型与表达式</h2>
<table>
<thead>
<tr>
<th>写法</th>
<th>含义</th>
<th>容易忽略的地方</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>name = "Python"</code></td>
<td>把值绑定到变量名</td>
<td><code>=</code> 是赋值，<code>==</code> 才是比较值是否相等</td>
</tr>
<tr>
<td><code>42</code>、<code>3.14</code>、<code>"42"</code></td>
<td>整数、浮点数、字符串</td>
<td><code>"42" + 1</code> 会报错；先用 <code>int("42")</code> 转换</td>
</tr>
<tr>
<td><code>True</code>、<code>False</code>、<code>None</code></td>
<td>布尔值与空值</td>
<td>判断是否为 <code>None</code>，通常写 <code>value is None</code></td>
</tr>
<tr>
<td><code>a and b</code>、<code>a or b</code>、<code>not a</code></td>
<td>逻辑运算</td>
<td><code>and</code>、<code>or</code> 返回操作数，不保证返回布尔值</td>
</tr>
<tr>
<td><code>f"学了 {minutes} 分钟"</code></td>
<td>格式化字符串</td>
<td>花括号里可以写表达式，如 <code>{minutes / 60:.1f}</code></td>
</tr>
</tbody>
</table>
<p><code>input()</code> 的结果始终是字符串；要计算分钟数，必须先转换，并考虑非法输入：</p>
<pre><code>raw = "45"  # 实际交互时可换成 input("学习了多少分钟？")
minutes = int(raw)
print(f"学习了 {minutes} 分钟")
</code></pre>
<p><code>bool("")</code> 是 <code>False</code>，但 <code>bool("False")</code> 是 <code>True</code>：非空字符串为真，字符串内容不会自动解析成布尔值。</p>
<h2>2. 容器：选对数据形状</h2>
<table>
<thead>
<tr>
<th>类型</th>
<th>常用场景</th>
<th>常用操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>list</code> 列表</td>
<td>有顺序、可增删的一组值</td>
<td><code>append()</code>、索引、切片</td>
</tr>
<tr>
<td><code>tuple</code> 元组</td>
<td>固定顺序、不打算增删的一组值</td>
<td>解包、索引</td>
</tr>
<tr>
<td><code>dict</code> 字典</td>
<td>用键查找字段或累计结果</td>
<td><code>data[key]</code>、<code>get()</code>、<code>items()</code></td>
</tr>
<tr>
<td><code>set</code> 集合</td>
<td>去重、判断成员是否存在</td>
<td><code>add()</code>、<code>in</code></td>
</tr>
</tbody>
</table>
<pre><code>records = [
    {"subject": "Python", "minutes": 45},
    {"subject": "英语", "minutes": 30},
]

print(records[0]["subject"])             # Python；列表索引从 0 开始
print(records[:1])                       # 返回包含第一条记录的新列表
print({row["subject"] for row in records})  # 去重后的科目集合
</code></pre>
<p><code>record["minutes"]</code> 在键不存在时抛出 <code>KeyError</code>；<code>record.get("minutes", 0)</code> 则返回默认值 <code>0</code>。后者适合累计计数，但<strong>不能</strong>用默认值掩盖必须存在的输入字段。</p>
<p>变量存放的是对对象的引用。<code>b = a</code> 不会复制列表；<code>a.copy()</code> 只复制最外层，嵌套的字典仍被共享：</p>
<pre><code>a = [{"minutes": 45}]
b = a.copy()
b[0]["minutes"] = 60
print(a[0]["minutes"])  # 60
</code></pre>
<h2>3. 控制流与推导式</h2>
<p><code>if</code> 选择分支，<code>for</code> 遍历数据，<code>continue</code> 跳过当前轮。Python 用缩进表示代码块：</p>
<pre><code>for index, record in enumerate(records, start=1):
    if record["minutes"] &lt;= 0:
        continue
    print(f'{index}. {record["subject"]}: {record["minutes"]} 分钟')
</code></pre>
<p><code>enumerate()</code> 同时给出序号和元素；<code>zip(a, b)</code> 可以并行遍历两组数据，但默认会在较短的一组结束时停止。</p>
<p>推导式适合简单的“筛选 + 转换”，不要把复杂的多层判断挤进一行：</p>
<pre><code>long_sessions = [row for row in records if row["minutes"] &gt;= 40]
total = sum(row["minutes"] for row in records)
</code></pre>
<p>第一行创建列表；第二行传给 <code>sum()</code> 的是生成器表达式，按需提供值，不先构造另一份列表。</p>
<h2>4. 函数、类型标注与异常</h2>
<p>函数用参数接收输入，用 <code>return</code> 交还结果。<code>print()</code> 只负责显示，不等于返回值。类型标注能帮助阅读和静态检查，<strong>不会自动校验运行时输入</strong>。</p>
<pre><code>def total_minutes(records: list[dict[str, object]]) -&gt; int:
    return sum(int(row["minutes"]) for row in records)
</code></pre>
<p>这段写法假设每条记录都有可转换的 <code>minutes</code>。当数据来自文件、网络或用户输入时，应先校验，再交给计算函数。也要避开可变默认参数：</p>
<pre><code>def add_subject(subject: str, subjects: list[str] | None = None) -&gt; list[str]:
    if subjects is None:
        subjects = []
    subjects.append(subject)
    return subjects
</code></pre>
<p>如果把默认值直接写成 <code>[]</code>，多次调用会共享同一个列表。上面每次未传入列表时都会新建一个。</p>
<p>异常处理只包住可能失败的操作，并捕获预期类型：</p>
<pre><code>try:
    minutes = int("四十五")
except ValueError as exc:
    print(f"分钟数无效：{exc}")
</code></pre>
<p><code>ValueError</code> 表示值的格式不合要求；文件打不开通常表现为 <code>OSError</code>。不要用宽泛的 <code>except Exception</code> 把自己写错的代码也当成普通输入错误。</p>
<h2>5. 文件、模块与脚本入口</h2>
<p><code>pathlib.Path</code> 处理路径，<code>with</code> 管理文件的打开和关闭。<code>Path(__file__).with_name("study.csv")</code> 指向<strong>脚本旁边</strong>的 CSV，运行命令时所在目录改变也不影响它。</p>
<pre><code>from pathlib import Path

source = Path(__file__).with_name("study.csv")
with source.open("r", encoding="utf-8") as file:
    text = file.read()
</code></pre>
<p><code>if __name__ == "__main__":</code> 用来区分“直接运行脚本”和“被其他文件导入”。把主要工作写进 <code>main()</code>，导入模块时就不会意外开始读文件、打印或退出。</p>
<h2>6. 完整脚本：统计 CSV 中的学习时间</h2>
<p>在同一个目录中创建 <code>study.csv</code>：</p>
<pre><code>date,subject,minutes
2026-10-07,Python,45
2026-10-08,英语,30
2026-10-08,Python,25
</code></pre>
<p>再创建 <code>study.py</code>。读取函数负责把外部文本变成经过校验的记录，统计函数只做计算，<code>main()</code> 负责输出和退出状态：</p>
<pre><code>import csv
import sys
from collections.abc import Iterable, Iterator
from dataclasses import dataclass
from datetime import date
from pathlib import Path


@dataclass(frozen=True)
class StudyRecord:
    day: date
    subject: str
    minutes: int


def read_records(path: Path) -&gt; Iterator[StudyRecord]:
    with path.open("r", encoding="utf-8-sig", newline="") as file:
        reader = csv.DictReader(file)
        if reader.fieldnames != ["date", "subject", "minutes"]:
            raise ValueError("表头必须是 date,subject,minutes")

        for row in reader:
            line_no = reader.line_num
            if None in row:
                raise ValueError(f"第 {line_no} 行：字段数量过多")

            day_text = (row.get("date") or "").strip()
            subject = (row.get("subject") or "").strip()
            minutes_text = (row.get("minutes") or "").strip()

            try:
                day = date.fromisoformat(day_text)
            except ValueError as exc:
                raise ValueError(f"第 {line_no} 行：无效日期 {day_text!r}") from exc

            if not subject:
                raise ValueError(f"第 {line_no} 行：科目不能为空")

            try:
                minutes = int(minutes_text)
            except ValueError as exc:
                raise ValueError(f"第 {line_no} 行：分钟数不是整数 {minutes_text!r}") from exc

            if minutes &lt;= 0:
                raise ValueError(f"第 {line_no} 行：分钟数必须大于 0")

            yield StudyRecord(day, subject, minutes)


def summarize(records: Iterable[StudyRecord]) -&gt; dict[str, int]:
    totals: dict[str, int] = {}
    for record in records:
        totals[record.subject] = totals.get(record.subject, 0) + record.minutes
    return totals


def main() -&gt; int:
    source = Path(__file__).with_name("study.csv")

    try:
        totals = summarize(read_records(source))
    except (OSError, ValueError) as exc:
        print(f"读取失败：{exc}", file=sys.stderr)
        return 1

    for subject, minutes in sorted(
        totals.items(), key=lambda item: (-item[1], item[0])
    ):
        print(f"{subject}：{minutes} 分钟")
    print(f"总计：{sum(totals.values())} 分钟")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
</code></pre>
<p>运行 <code>python study.py</code>，Windows 上也可以使用 <code>py study.py</code>。预期输出：</p>
<pre><code>Python：70 分钟
英语：30 分钟
总计：100 分钟
</code></pre>
<h3>脚本里的几个关键连接点</h3>
<ul>
<li><strong>文本到对象</strong>：<code>csv.DictReader</code> 读到的正常单元格是字符串，缺失字段可能得到 <code>None</code>；<code>date.fromisoformat()</code> 和 <code>int()</code> 在入口处转换并校验。<code>StudyRecord</code> 的标注不会代替这一步。</li>
<li><strong>生成器到统计函数</strong>：<code>read_records()</code> 中的 <code>yield</code> 使它按需读取。调用函数本身还没有打开文件；<code>summarize()</code> 遍历它时才开始读取。因此 <code>try</code> 要包住整个统计调用。</li>
<li><strong>文件生命周期</strong>：完整遍历或读取函数抛错时，<code>with</code> 会关闭文件。如果只取生成器的前几条就停止，文件可能仍保持打开；需要显式关闭生成器，或为小文件改用列表。</li>
<li><strong>错误到退出码</strong>：读取层知道出错行号，就抛带行号的 <code>ValueError</code>；<code>main()</code> 决定把信息写到标准错误输出并返回 <code>1</code>。正常完成返回 <code>0</code>。</li>
<li><strong>内存使用</strong>：读取阶段不保存全部行；统计字典的大小主要由不同科目的数量决定。</li>
</ul>
<h2>7. 自测与扩展</h2>
<p>先手动把第三行改成 <code>2026-10-08,英语,-5</code>：脚本应报告“第 3 行：分钟数必须大于 0”。把日期改成 <code>2026-02-30</code>、删除文件、清空文件内容，也能检查对应的错误分支。CSV 为空时表头缺失，脚本会报告表头错误；只有表头而没有数据时，总计是 <code>0</code>。</p>
<p>统计函数可以脱离文件单独验证。把下面的代码另存为同目录的 <code>test_study.py</code>，运行 <code>python test_study.py</code>：</p>
<pre><code>from datetime import date
from study import StudyRecord, summarize

records = [
    StudyRecord(date(2026, 10, 7), "Python", 45),
    StudyRecord(date(2026, 10, 8), "Python", 25),
]
assert summarize(records) == {"Python": 70}
assert summarize([]) == {}
</code></pre>
<p>下一步可以写一个按日期汇总的函数。保持 <code>read_records()</code> 不变，只改变统计键，再比较两份汇总代码有哪些可以复用。这样练习比单纯背诵更多语法更接近实际写脚本的过程。</p>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Study"/>
  </entry>
  <entry>
    <title>XML 基础与 Odoo XML 写法</title>
    <link href="https://www.pardofelis.top/posts/261f7a2c/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/261f7a2c/</id>
    <published>2026-10-12T00:00:00.000Z</published>
    <updated>2026-10-12T00:00:00.000Z</updated>
    <summary>XML 的结构、转义、解析与 XPath，以及 Odoo 的数据记录、视图、菜单和 QWeb 模板。</summary>
    <content type="html"><![CDATA[<h1>XML 基础与 Odoo XML 写法</h1>
<p>XML（可扩展标记语言）用标签描述有层次的数据。它规定文档如何组织；<code>&lt;book&gt;</code>、<code>&lt;field&gt;</code> 等标签的具体含义由使用 XML 的程序决定。Odoo 就用 XML 声明数据、视图、菜单和模板。</p>
<h2>基础语法</h2>
<p>一个 XML 文档必须有且只有一个根元素。标签区分大小写，开始和结束标签要同名、正确嵌套；没有内容的元素可以写成自闭合形式。</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;library&gt;
    &lt;book id="b1"&gt;
        &lt;title&gt;XML 入门&lt;/title&gt;
        &lt;author&gt;小明&lt;/author&gt;
    &lt;/book&gt;
    &lt;book id="b2" /&gt;
&lt;/library&gt;
</code></pre>
<p>XML 声明通常放在文件开头，常见版本是 <code>1.0</code>，声明本身可以省略。<code>&lt;library&gt;</code> 是根元素。<code>&lt;book id="b2" /&gt;</code> 等价于 <code>&lt;book id="b2"&gt;&lt;/book&gt;</code>。<code>&lt;a&gt;&lt;b&gt;&lt;/a&gt;&lt;/b&gt;</code> 则不合法，因为标签交叉了。</p>
<h3>属性与注释</h3>
<p>属性写在开始标签中；属性值必须用成对的单引号或双引号包裹，同一元素不能重复使用同一个属性名。名称不能以数字开头。实际编写时可选用字母开头，再使用字母、数字、下划线或连字符，避免与工具的命名限制冲突。</p>
<pre><code>&lt;book id="b1" language="zh-CN" available="true" /&gt;
&lt;!-- 这是注释，不能包含连续的两个连字符 --&gt;
</code></pre>
<p>XML 没有内置的布尔属性类型：这里的 <code>"true"</code> 首先是文本，是否当作布尔值由读取它的程序决定。</p>
<h3>特殊字符与 CDATA</h3>
<p>文本中的 <code>&lt;</code> 和 <code>&amp;</code> 必须转义；属性值中还要转义与外层引号相同的引号。五个预定义实体如下：</p>
<table>
<thead>
<tr>
<th>写法</th>
<th>表示的字符</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&amp;lt;</code></td>
<td><code>&lt;</code></td>
</tr>
<tr>
<td><code>&amp;gt;</code></td>
<td><code>&gt;</code></td>
</tr>
<tr>
<td><code>&amp;amp;</code></td>
<td><code>&amp;</code></td>
</tr>
<tr>
<td><code>&amp;apos;</code></td>
<td><code>'</code></td>
</tr>
<tr>
<td><code>&amp;quot;</code></td>
<td><code>"</code></td>
</tr>
</tbody>
</table>
<p>例如，<code>Tom &amp; Jerry</code> 应写成 <code>Tom &amp;amp; Jerry</code>；<code>&lt;rule test="a &amp;lt; b"&gt;</code> 中的比较符也需要转义。CDATA 可以在<strong>元素文本</strong>中保留大段未经转义的字符，但不能用于属性值，也不能在内部直接出现 <code>]]&gt;</code>：</p>
<pre><code>&lt;example&gt;&lt;![CDATA[if a &lt; b and c &gt; d: print("ok")]]&gt;&lt;/example&gt;
</code></pre>
<p>CDATA 不会让其中的内容变成 XML 子元素；解析器读到的是一段文本。</p>
<h2>解析、遍历与添加节点</h2>
<p>Python 标准库的 <code>xml.etree.ElementTree</code> 足够演示常见操作。假设 <code>books.xml</code> 保存了前面的 <code>&lt;library&gt;</code> 示例：</p>
<pre><code>import xml.etree.ElementTree as ET

tree = ET.parse("books.xml")
root = tree.getroot()

for book in root.findall("book"):
    print(book.get("id"), book.findtext("title"))

new_book = ET.SubElement(root, "book", {"id": "b3"})
ET.SubElement(new_book, "title").text = "新书"
ET.SubElement(new_book, "author").text = "小李"
tree.write("books-updated.xml", encoding="utf-8", xml_declaration=True)
</code></pre>
<p><code>parse()</code> 读取文件，<code>getroot()</code> 取得根元素，<code>findall()</code> 查找子节点，<code>SubElement()</code> 添加节点。示例中的第二本书没有 <code>&lt;title&gt;</code>，因此它的 <code>findtext("title")</code> 会返回 <code>None</code>，获取元素的属性值用<code>get()</code>,<code>findtext()</code>查找元素并返回其文本</p>
<h2>XPath 定位节点</h2>
<p>XPath 用路径和条件定位 XML 元素：</p>
<table>
<thead>
<tr>
<th>表达式</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/library/book</code></td>
<td>根元素下的直接 <code>book</code> 子元素</td>
</tr>
<tr>
<td><code>//book</code></td>
<td>任意层级的 <code>book</code> 元素</td>
</tr>
<tr>
<td><code>//book[@id='b1']</code></td>
<td><code>id</code> 为 <code>b1</code> 的 <code>book</code> 元素</td>
</tr>
<tr>
<td><code>//book/title</code></td>
<td>所有 <code>book</code> 下的 <code>title</code> 元素</td>
</tr>
<tr>
<td><code>"book"</code></td>
<td>当前节点的直接子元素 <code>book</code></td>
</tr>
<tr>
<td><code>"book/title"</code></td>
<td>当前节点的直接子元素 <code>book</code> 的直接子元素 <code>title</code></td>
</tr>
<tr>
<td><code>".//book"</code></td>
<td>当前节点下任意深度的 <code>book</code></td>
</tr>
<tr>
<td><code>".//book/title"</code></td>
<td>当前节点下任意深度的 <code>book</code> 的直接子元素 <code>title</code></td>
</tr>
<tr>
<td><code>"."</code></td>
<td>当前节点本身</td>
</tr>
</tbody>
</table>
<p>Python 的 <code>ElementTree</code> 只支持 XPath 的一个子集。从当前元素查询时通常写相对路径，例如 <code>root.findall("./book[@id='b1']")</code>。Odoo 的视图继承也使用 XPath 表达式定位已有视图中的节点。</p>
<h2>Odoo 中的 XML</h2>
<p>以下示例以 <strong>Odoo 19</strong> 的模块写法为准。Odoo XML 文件通常以 <code>&lt;odoo&gt;</code> 为根元素，按文件中的顺序执行声明；引用的记录要先定义，或者来自已在模块依赖中声明的模块。XML 文件还必须列入模块的 <code>__manifest__.py</code>，安装或升级模块时才会加载。</p>
<pre><code># __manifest__.py
{
    "name": "Library Demo",
    "depends": ["base"],
    "data": [
        "views/library_book_views.xml",
        "data/library_book_data.xml",
    ],
}
</code></pre>
<p>文件顺序有意义：后一个文件可以引用前一个文件建立的 XML ID。下面的 <code>library.book</code> 是示例模型，需先在模块的 Python 代码中定义 <code>name</code>、<code>author</code> 和 <code>note</code> 字段；仅写 XML 不会自动创建这个业务模型。</p>
<h3>创建数据记录</h3>
<p><code>record</code> 的 <code>model</code> 指向要写入的 Odoo 模型，<code>id</code> 是该记录的 XML ID。<code>field</code> 用于设置字段；纯文本、<code>ref</code> 与 <code>eval</code> 分别适合普通值、已有 XML ID 和需要求值的表达式。</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;odoo&gt;
    &lt;record id="book_xml_intro" model="library.book"&gt;
        &lt;field name="name"&gt;XML 入门&lt;/field&gt;
        &lt;field name="author"&gt;小明&lt;/field&gt;
    &lt;/record&gt;

    &lt;record id="book_odoo_intro" model="library.book"&gt;
        &lt;field name="name"&gt;Odoo 入门&lt;/field&gt;
        &lt;field name="author"&gt;小李&lt;/field&gt;
    &lt;/record&gt;
&lt;/odoo&gt;
</code></pre>
<p>同一模块里可直接用 <code>book_xml_intro</code> 引用它；跨模块引用时写完整的 <code>模块名.book_xml_intro</code>。例如 <code>&lt;field name="company_id" ref="base.main_company"/&gt;</code> 会把一个 Many2one 字段指向已有公司。布尔值等类型可以用 <code>eval</code>，例如 <code>&lt;field name="active" eval="True"/&gt;</code>。不要把 <code>ref</code> 或 <code>eval</code> 当成普通文本写进标签体内。</p>
<h3>定义列表、表单、动作和菜单</h3>
<p>视图本身也是 <code>ir.ui.view</code> 记录；界面结构写在 <code>arch</code> 字段内。动作把模型与视图类型关联起来，菜单再指向动作。以下内容可放在 <code>views/library_book_views.xml</code>：</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;odoo&gt;
    &lt;record id="view_library_book_list" model="ir.ui.view"&gt;
        &lt;field name="name"&gt;library.book.list&lt;/field&gt;
        &lt;field name="model"&gt;library.book&lt;/field&gt;
        &lt;field name="arch" type="xml"&gt;
            &lt;list string="图书"&gt;
                &lt;field name="name" /&gt;
                &lt;field name="author" /&gt;
            &lt;/list&gt;
        &lt;/field&gt;
    &lt;/record&gt;

    &lt;record id="view_library_book_form" model="ir.ui.view"&gt;
        &lt;field name="name"&gt;library.book.form&lt;/field&gt;
        &lt;field name="model"&gt;library.book&lt;/field&gt;
        &lt;field name="arch" type="xml"&gt;
            &lt;form string="图书"&gt;
                &lt;sheet&gt;
                    &lt;group&gt;
                        &lt;field name="name" /&gt;
                        &lt;field name="author" /&gt;
                    &lt;/group&gt;
                &lt;/sheet&gt;
            &lt;/form&gt;
        &lt;/field&gt;
    &lt;/record&gt;

    &lt;record id="action_library_books" model="ir.actions.act_window"&gt;
        &lt;field name="name"&gt;图书&lt;/field&gt;
        &lt;field name="res_model"&gt;library.book&lt;/field&gt;
        &lt;field name="view_mode"&gt;list,form&lt;/field&gt;
    &lt;/record&gt;

    &lt;menuitem id="menu_library_root" name="图书馆" sequence="10" /&gt;
    &lt;menuitem id="menu_library_books" name="图书" parent="menu_library_root"
              action="action_library_books" sequence="10" /&gt;
&lt;/odoo&gt;
</code></pre>
<p><code>view_mode</code> 里的类型用逗号连接，不加空格。<code>list</code> 是此处 Odoo 19 的列表视图名称；旧版本资料可能写 <code>tree</code>。菜单能否实际显示和访问还受模型访问权限影响，通常还需在 <code>security/ir.model.access.csv</code> 中给相应用户组配置权限。</p>
<h3>用 XPath 继承视图</h3>
<p>修改已有视图时，创建一条带 <code>inherit_id</code> 的新 <code>ir.ui.view</code> 记录，再在 <code>arch</code> 中定位目标。下面的示例继承上面定义的表单视图，在 <code>author</code> 字段之后加入预先定义好的 <code>note</code> 字段：</p>
<pre><code>&lt;record id="view_library_book_form_note" model="ir.ui.view"&gt;
    &lt;field name="name"&gt;library.book.form.note&lt;/field&gt;
    &lt;field name="model"&gt;library.book&lt;/field&gt;
    &lt;field name="inherit_id" ref="view_library_book_form" /&gt;
    &lt;field name="arch" type="xml"&gt;
        &lt;xpath expr="//field[@name='author']" position="after"&gt;
            &lt;field name="note" /&gt;
        &lt;/xpath&gt;
    &lt;/field&gt;
&lt;/record&gt;
</code></pre>
<p>这段记录应放在原视图记录<strong>之后</strong>、<code>&lt;/odoo&gt;</code> <strong>之前</strong>。跨模块继承时，<code>inherit_id</code> 一般写成 <code>原模块名.视图 XML ID</code>，并在 manifest 的 <code>depends</code> 中声明该模块。常见 <code>position</code> 有 <code>before</code>、<code>after</code>、<code>inside</code>、<code>replace</code> 和 <code>attributes</code>；定位表达式必须能匹配已有视图节点。</p>
<h3>只在安装时加载的数据</h3>
<p>不希望模块升级时反复覆盖的初始数据，可以放在 <code>&lt;data noupdate="1"&gt;</code> 中；普通视图配置一般不应随意加这个标记，否则升级后修改可能无法按预期应用。</p>
<pre><code>&lt;odoo&gt;
    &lt;data noupdate="1"&gt;
        &lt;record id="book_sample" model="library.book"&gt;
            &lt;field name="name"&gt;初始样书&lt;/field&gt;
        &lt;/record&gt;
    &lt;/data&gt;
&lt;/odoo&gt;
</code></pre>
<h3>QWeb 模板也是 XML</h3>
<p>Odoo 的 QWeb 模板在 XML 元素上使用 <code>t-*</code> 指令控制输出。下面只演示模板语法；实际放入报告或网页时，还要按相应场景声明模板并传入 <code>books</code> 数据。</p>
<pre><code>&lt;t t-name="library_demo.book_list"&gt;
    &lt;ul&gt;
        &lt;li t-foreach="books" t-as="book"&gt;
            &lt;t t-out="book.name" /&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/t&gt;
</code></pre>
<p><code>t-foreach</code> 遍历集合，<code>t-as</code> 命名当前项，<code>t-out</code> 输出内容并进行 HTML 转义。QWeb 指令的具体表达式运行环境取决于模板运行在服务端还是浏览器端，不要把两种环境的对象直接混用。</p>
<h2>参考资料</h2>
<ul>
<li><a href="https://www.w3.org/TR/xml/">W3C：XML 1.0 规范</a></li>
<li><a href="https://www.odoo.com/documentation/19.0/developer/reference/backend/module.html">Odoo 19：模块清单</a></li>
<li><a href="https://www.odoo.com/documentation/19.0/developer/reference/backend/data.html">Odoo 19：XML 数据文件</a></li>
<li><a href="https://www.odoo.com/documentation/19.0/developer/reference/user_interface/view_records.html">Odoo 19：视图记录与继承</a></li>
<li><a href="https://www.odoo.com/documentation/19.0/developer/reference/backend/actions.html">Odoo 19：窗口动作</a></li>
<li><a href="https://www.odoo.com/documentation/19.0/developer/reference/frontend/qweb.html">Odoo 19：QWeb 模板</a></li>
</ul>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Study"/>
  </entry>
  <entry>
    <title>MySQL 进阶学习</title>
    <link href="https://www.pardofelis.top/posts/46d4aa72/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/46d4aa72/</id>
    <published>2026-10-09T22:35:41.000Z</published>
    <updated>2026-10-11T17:56:16.000Z</updated>
    <summary>整理 MySQL 索引、查询性能分析、视图与存储程序等进阶知识。</summary>
    <content type="html"><![CDATA[<p>以下尖括号表示需要替换的名称或值，方括号表示可选部分；执行前应删除占位符。</p>
<h3>索引</h3>
<p>是帮助MySQL高效获取数据的数据结构，以空间换时间</p>
<blockquote>
<p>优劣
优势：
提高检索数据的效率，降低数据库的IO成本，降低CPU的消耗
劣势：
索引需要占用空间；<code>INSERT</code>、<code>UPDATE</code>、<code>DELETE</code> 还需维护相关索引，可能增加写入成本。</p>
</blockquote>
<p>MySQL的索引在存储引擎实现的，不同引擎有不同的结构
B+树：最常见的，大部分都支持B+树索引
Hash：支持等值查找，不支持范围查找或按索引排序。<code>MEMORY</code> 引擎支持显式 Hash 索引，InnoDB 也可能自动构建自适应 Hash 索引。
R-树：用于空间索引；InnoDB 和 MyISAM 都支持空间索引。
全文索引：用于文本关键字检索；InnoDB 和 MyISAM 支持 <code>FULLTEXT</code> 索引。</p>
<h4>索引结构</h4>
<blockquote>
<p>二叉树
缺点：顺序插入时会形成一个链表，查询性能会大大降低。大数据量的情况下，层级较深，检索速度慢
Hash
通过哈希函数把键映射到槽位；发生哈希冲突时，存储引擎需要进一步比较或处理冲突。</p>
</blockquote>
<ol>
<li>Hash索引仅支持对等比较（=，IN），不支持范围查询（between，&gt;，&lt;，。。。）</li>
<li>无法通过索引完成排序操作</li>
<li>等值查询通常很快，但实际性能取决于数据分布、冲突和执行计划，不能一概认为比 B+ 树快。</li>
</ol>
<p>MySQL中的InnoDB对Hash有自适应，Hash索引是存储引擎根据B+树索引在指定条件下自动构建的</p>
<h4>索引分类</h4>
<table>
<thead>
<tr>
<th>分类</th>
<th>含义</th>
<th>特点</th>
<th>关键字</th>
</tr>
</thead>
<tbody>
<tr>
<td>主键索引</td>
<td>针对表中主键创建的索引</td>
<td>默认自动创建且只能有一个</td>
<td>PRIMARY</td>
</tr>
<tr>
<td>唯一索引</td>
<td>避免某个字段的值出现重复</td>
<td>可以有多个</td>
<td>UNIQUE</td>
</tr>
<tr>
<td>常规索引</td>
<td>快速定位特点数据</td>
<td>可以有多个</td>
<td>-</td>
</tr>
<tr>
<td>全文索引</td>
<td>全文索引查找的是文本中的关键字，而不是比较索引中的值</td>
<td>可以有多个</td>
<td>FULLTEXT</td>
</tr>
</tbody>
</table>
<blockquote>
<p>根据存储形式划分</p>
</blockquote>
<table>
<thead>
<tr>
<th>分类</th>
<th>含义</th>
<th>特点</th>
</tr>
</thead>
<tbody>
<tr>
<td>聚集索引</td>
<td>InnoDB 聚集索引叶子节点保存行数据</td>
<td>每张 InnoDB 表有一个</td>
</tr>
<tr>
<td>二级索引</td>
<td>数据与索引分开存放，叶子节点为对应行的主键</td>
<td>可以有很多个</td>
</tr>
</tbody>
</table>
<p>聚集索引选取规则：如果存在主键，主键索引就是聚集索引
如果没有主键，InnoDB 会选择所有列都为 <code>NOT NULL</code> 的第一个 <code>UNIQUE</code> 索引。
如果也没有这样的唯一索引，InnoDB 会生成隐藏的聚集索引。</p>
<blockquote>
<p>由二级索引找到主键值，再到聚集索引读取行数据，称为回表查询。</p>
</blockquote>
<h4>索引语法</h4>
<blockquote>
<p>创建索引</p>
</blockquote>
<pre><code>CREATE [UNIQUE|FULLTEXT] INDEX &lt;INDEXNAME&gt; ON &lt;TABLENAME&gt;(COLUMN,COLUMN,...);
</code></pre>
<blockquote>
<p>查看索引</p>
</blockquote>
<pre><code>SHOW INDEX FROM &lt;TABLENAME&gt;;
</code></pre>
<blockquote>
<p>删除索引</p>
</blockquote>
<pre><code>DROP INDEX &lt;INDEXNAME&gt; ON &lt;TABLENAME&gt;;
</code></pre>
<h4>SQL性能分析</h4>
<p><code>SHOW STATUS</code> 可查看服务器状态变量。下面的 <code>Com_%</code> 计数是服务器级别的语句计数，不限定当前数据库；也不等同于实际读取或修改的行数。</p>
<pre><code>SHOW GLOBAL STATUS LIKE 'Com\_%';
</code></pre>
<blockquote>
<p>慢查询日志
慢查询日志记录了所有执行时间超过指定参数（long_query_time，单位秒，默认10）的所有SQL语句的日志。默认不开启。需要在配置文件中配置：slow_query_log=1  和long_query_time=10。配置完成之后重启服务器</p>
</blockquote>
<blockquote>
<p>profile详情
可以帮助了解SQL语句时间消耗
可通过 <code>have_profiling</code> 查看当前 MySQL 是否支持该功能：</p>
</blockquote>
<pre><code>SELECT @@have_profiling;
</code></pre>
<p><code>SHOW PROFILE</code> 已弃用；旧版本中可在当前会话启用，新的性能分析应优先使用 Performance Schema。</p>
<pre><code>SET profiling=1;
</code></pre>
<pre><code>-- 查看当前会话中各语句的耗时
SHOW profiles;

-- 查看指定 query_id 的各阶段耗时
SHOW PROFILE FOR QUERY &lt;QUERY_ID&gt;;

-- 查看指定 query_id 的 CPU 耗时
SHOW PROFILE CPU FOR QUERY &lt;QUERY_ID&gt;;
</code></pre>
<blockquote>
<p>explain执行计划
EXPLAIN或者DESC命令获取MySQL任何执行SELECT语句的信息，包括SELECT语句执行过程中表如何连接和连接顺序。</p>
</blockquote>
<pre><code>-- 在 SELECT 语句前加 EXPLAIN
EXPLAIN SELECT &lt;COLUMN&gt; FROM &lt;TABLENAME&gt;;
</code></pre>
<p>各个字段含义</p>
<ul>
<li>Id
<ul>
<li><code>SELECT</code> 的标识符；不能只凭 <code>id</code> 大小推断实际执行顺序。</li>
</ul>
</li>
<li>select_type
<ul>
<li>表示select的类型，常见的取值由simple（简单表，即不使用表连接或子查询）、Primary（主查询，即外层的查询）、UNION（UNION中的第二以及以后的查询语句）、SUBQUERY（SELECT/WHERE之后包含了子查询）等</li>
</ul>
</li>
<li>type
<ul>
<li>表访问类型，常见值有 <code>system</code>、<code>const</code>、<code>eq_ref</code>、<code>ref</code>、<code>range</code>、<code>index</code>、<code>ALL</code>；需结合行数和过滤条件判断性能。</li>
</ul>
</li>
<li>possible_keys
<ul>
<li>显示可能应用在这张表上的索引，一个或多个</li>
</ul>
</li>
<li>key
<ul>
<li>实际使用的索引。如果为NULL，就是没有使用索引</li>
</ul>
</li>
<li>key_len
<ul>
<li>优化器选用的索引键长度，可帮助判断联合索引用了哪些列；不是越短越好。</li>
</ul>
</li>
<li>rows
<ul>
<li>MySQL认为必须要执行查询的行数，在InnoDB引擎的表中是一个估计值，可能并不总是准确的</li>
</ul>
</li>
<li>filtered
<ul>
<li>估计经过表条件筛选后保留的行占 <code>rows</code> 的百分比；需结合实际执行计划判断，不能单凭数值高低评价查询。</li>
</ul>
</li>
</ul>
<h4>索引的使用</h4>
<blockquote>
<p>最左前缀法则
联合索引通常按最左列开始使用；跳过前导列或遇到范围条件后，后续列能否用于索引查找取决于查询条件和优化器。用 <code>EXPLAIN</code> 确认实际使用情况。
索引列运算
对索引列直接做函数或运算，通常会妨碍普通索引用于查找；可考虑改写条件或使用函数索引，并用 <code>EXPLAIN</code> 验证。
字符串不加引号
字符串值应使用引号。若比较时发生隐式类型转换，可能妨碍索引使用，具体取决于列类型和执行计划。
模糊查询
<code>LIKE 'aa%'</code> 有机会利用 B+ 树索引做范围查找；<code>LIKE '%aa'</code> 通常不能使用普通 B+ 树索引定位前缀。
or连接的条件
<code>OR</code> 的各条件能否用索引，取决于可用索引、数据分布和优化器选择；可检查 <code>EXPLAIN</code> 中是否使用 Index Merge 或全表扫描。
数据分布影响
如果MySQL评估索引比全表慢，则不使用索引
SQL提示
在SQL语句中加入一些人为的提示优化操作</p>
</blockquote>
<pre><code>-- USE INDEX
SELECT * FROM &lt;TABLENAME&gt; USE INDEX(&lt;INDEXNAME&gt;) WHERE &lt;OPTION&gt;;

-- IGNORE INDEX
SELECT * FROM &lt;TABLENAME&gt; IGNORE INDEX(&lt;INDEXNAME&gt;) WHERE &lt;OPTION&gt;;

-- FORCE INDEX
SELECT * FROM &lt;TABLENAME&gt; FORCE INDEX(&lt;INDEXNAME&gt;) WHERE &lt;OPTION&gt;;
</code></pre>
<blockquote>
<p>覆盖索引
当查询需要的列都可从索引取得时，可避免回表；按实际查询选择列，避免无必要的 <code>SELECT *</code>。</p>
</blockquote>
<blockquote>
<p>前缀索引
对较长的字符串列，可只索引前缀以减少索引占用；前缀过短会降低区分度，需要按数据分布选择长度。</p>
</blockquote>
<pre><code>CREATE INDEX &lt;INDEX_NAME&gt; ON &lt;TABLE_NAME&gt; (&lt;COLUMN_NAME&gt;(&lt;PREFIX_LENGTH&gt;));
</code></pre>
<p>可用 <code>COUNT(DISTINCT LEFT(列名, N)) / COUNT(*)</code> 估计前缀的区分度。区分度越高通常越利于筛选，但实际性能仍需结合索引大小和查询计划衡量。</p>
<blockquote>
<p>单列索引与联合索引
单列：一个索引只包含一列
多列：一个索引包含多列
多个条件经常一起出现时，可考虑符合列顺序的联合索引；单列索引与联合索引的取舍应依据实际查询、写入成本和 <code>EXPLAIN</code> 结果。</p>
</blockquote>
<h4>索引设计原则</h4>
<ol>
<li>针对数据量大、且查询比较频繁的表建立索引。</li>
<li>对常作为查询条件、分组、排序的字段建立索引。</li>
<li>尽量选择区分度高的列作为索引，尽量建立唯一索引，区分度越高，使用索引的效率越高。</li>
<li>如果是字符串类型的索引，字符串长度较长，可以根据字段的特点建立前缀索引。</li>
<li>联合索引可覆盖部分常用查询并减少回表，但不一定比单列索引节省空间。</li>
<li>要控制索引数量，索引不是越多越好，索引越多，维护索引的代价越大，影响增删改的效率。</li>
<li>如果索引列不能存储NULL，在建表时候用NOT NULL限制，当优化器指定每列是否包含NULL时，它可以更好得确定哪个索引最有效。</li>
</ol>
<h3>视图</h3>
<p>视图保存查询定义，通常不单独存储结果数据；查询视图时会访问其底层表。</p>
<h4>语法</h4>
<blockquote>
<p>创建或修改视图</p>
</blockquote>
<pre><code>CREATE [OR REPLACE] VIEW &lt;VIEWNAME&gt;(NEWCOLUMNNAME1,NEWCOLUMNNAME2,...) AS &lt;SELECT语句&gt; [WITH [CASCADED | LOCAL] CHECK OPTION];
</code></pre>
<blockquote>
<p>查询视图</p>
</blockquote>
<pre><code>-- 查看创建视图语句
SHOW CREATE VIEW &lt;VIEWNAME&gt;;

-- 查询视图数据
SELECT * FROM &lt;VIEWNAME&gt;;
</code></pre>
<blockquote>
<p>删除视图</p>
</blockquote>
<pre><code>DROP VIEW &lt;VIEWNAME&gt;;
</code></pre>
<p>视图的检查选项：
使用 <code>WITH CHECK OPTION</code> 创建可更新视图后，经该视图执行的 <code>INSERT</code> 和 <code>UPDATE</code> 必须使新行符合视图条件。
默认是 <code>CASCADED</code>。若视图 A 基于视图 B：<code>CASCADED</code> 会递归检查底层视图条件；<code>LOCAL</code> 检查 A 的条件，并继续按 B 自身的检查选项判断，不会强制给 B 添加检查选项。</p>
<h4>视图的更新</h4>
<p>要使得视图可以更新，视图与基表的行必须为一对一关系。如果视图包含以下任何一项，则视图无法更新：</p>
<ol>
<li>distinct</li>
<li>group by</li>
<li>having</li>
<li>UNION or UNION ALL</li>
<li>聚合函数或窗口函数</li>
</ol>
<h4>视图的作用</h4>
<ol>
<li>视图可以封装常用查询，减少重复书写查询条件。</li>
<li>可以向用户授予视图权限，并通过视图定义限制其可见的数据；权限效果还取决于视图的安全上下文和底层表权限。</li>
<li>视图可以帮助用户屏蔽真实表结构变化带来的影响</li>
</ol>
<h3>存储过程</h3>
<p>存储过程是保存在数据库中的 SQL 语句集合，可封装重复的数据库操作。</p>
<h4>语法</h4>
<blockquote>
<p>创建</p>
</blockquote>
<pre><code>CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
    SQL语句;
END;
</code></pre>
<blockquote>
<p>调用</p>
</blockquote>
<pre><code>CALL 名称(参数);
</code></pre>
<blockquote>
<p>查看数据库的存储过程</p>
</blockquote>
<pre><code>SELECT * FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_SCHEMA = '数据库名称' AND ROUTINE_TYPE = 'PROCEDURE';
</code></pre>
<blockquote>
<p>查看存储过程创建语句</p>
</blockquote>
<pre><code>SHOW CREATE PROCEDURE 存储过程名称;
</code></pre>
<blockquote>
<p>删除存储过程</p>
</blockquote>
<pre><code>DROP PROCEDURE [IF EXISTS] 存储过程名称;
</code></pre>
<h4>变量</h4>
<p>在 <code>mysql</code> 命令行客户端中定义多语句存储程序时，需要临时更改语句结束符；<code>DELIMITER</code> 是客户端命令，不是 SQL。</p>
<pre><code>-- 以下示例在 mysql 命令行客户端执行
DELIMITER //
CREATE PROCEDURE hello()
BEGIN
    SELECT 'hello' AS message;
END//
DELIMITER ;
</code></pre>
<h5>系统变量</h5>
<p>由MySQL数据库提供，不需要用户定义（全局变量GLOBAL，会话变量SESSION）</p>
<blockquote>
<p>查看系统变量</p>
</blockquote>
<pre><code>SHOW [GLOBAL | SESSION] VARIABLES;

SHOW [GLOBAL | SESSION] VARIABLES LIKE '变量名模式';

SELECT @@SESSION.系统变量名;
SELECT @@GLOBAL.系统变量名;
</code></pre>
<blockquote>
<p>设置系统变量</p>
</blockquote>
<pre><code>SET [SESSION | GLOBAL] 变量名=值;

SET @@SESSION.变量名=值;
SET @@GLOBAL.变量名=值;
</code></pre>
<h5>用户变量</h5>
<p>用户根据业务需求定义的。两个@是系统变量，一个@是用户变量，用户变量不需要提前声明类型。作用域为当前会话。
用户变量无需声明；读取尚未赋值的用户变量会得到 <code>NULL</code>。</p>
<blockquote>
<p>赋值</p>
</blockquote>
<pre><code>SET @变量名=值,@变量名=值,...;

SELECT @变量名:=值,@变量名:=值,...;
-- SELECT设置变量，必须要要用:=，SET的话:=和=都可
</code></pre>
<blockquote>
<p>使用</p>
</blockquote>
<pre><code>SELECT @变量名;
</code></pre>
<h5>局部变量</h5>
<p>由DECLARE声明，只能在BEGIN和END之间使用</p>
<blockquote>
<p>声明</p>
</blockquote>
<pre><code>DECLARE 变量名 类型 [DEFAULT ...];
</code></pre>
<p>字段类型有CHAR, BIGINT, INT,VARCHAR, DATE, TIME等</p>
<blockquote>
<p>赋值</p>
</blockquote>
<pre><code>SET 变量=值;
SET 变量:=值;
SELECT 字段 INTO 变量 FROM 表...;
</code></pre>
<p><code>SELECT ... INTO</code> 应只返回一行数据；局部变量名不带 <code>@</code>。</p>
<h4>IF判断</h4>
<pre><code>IF 条件 THEN
  ...
ELSEIF 条件2 THEN
  ...
ELSE
  ...
END IF;
</code></pre>
<h4>参数（形参）</h4>
<ul>
<li>IN
<ul>
<li>需要调用时输入的，是默认的</li>
</ul>
</li>
<li>OUT
<ul>
<li>作为返回值的参数</li>
</ul>
</li>
<li>INOUT
<ul>
<li>既可以作为输入，也可以作为输出</li>
</ul>
</li>
</ul>
<blockquote>
<p>用法</p>
</blockquote>
<pre><code>-- 使用SET对参数2进行赋值
CREATE PROCEDURE 存储过程(IN 参数1 类型, OUT 参数2 类型,...)
BEGIN
    SQL语句;
END;
</code></pre>
<h4>CASE</h4>
<blockquote>
<p>语法一</p>
</blockquote>
<pre><code>CASE 变量
    WHEN 值1 THEN SQL语句;
    WHEN 值2 THEN SQL语句;
    ...
    ELSE SQL语句;
END CASE;
</code></pre>
<blockquote>
<p>语法二</p>
</blockquote>
<pre><code>CASE
    WHEN 条件1 THEN SQL语句;
    WHEN 条件2 THEN SQL语句;
    ...
    ELSE SQL语句;
END CASE;
</code></pre>
<h4>WHILE</h4>
<pre><code>WHILE 条件 DO
    SQL语句;
END WHILE;
</code></pre>
<h4>REPEAT</h4>
<p>有条件的循环，满足条件则退出。</p>
<pre><code>REPEAT
    SQL语句;
UNTIL 条件
END REPEAT;
</code></pre>
<h4>LOOP</h4>
<p>可以实现简单的死循环，不在SQL中增加退出循环的条件。</p>
<p>LEAVE: 退出循环
ITERATE: 必须在循环中，表示跳过当前循环剩下的语句。</p>
<pre><code>[开始标记]:LOOP
    SQL语句;
    -- 根据条件选择 LEAVE 或 ITERATE，通常不会连续执行二者
    LEAVE 开始标记;
END LOOP [结束标记];
</code></pre>
<h4>游标</h4>
<p>用来存储查询结果集的数据类型，可以用游标进行循环处理。游标的使用包括声明、OPEN、FETCH、CLOSE。</p>
<blockquote>
<p>声明</p>
</blockquote>
<pre><code>DECLARE 游标名 CURSOR FOR 查询语句;
</code></pre>
<blockquote>
<p>打开游标</p>
</blockquote>
<pre><code>OPEN 游标名;
</code></pre>
<blockquote>
<p>获取游标记录</p>
</blockquote>
<pre><code>FETCH 游标名 INTO 变量1,变量2,...;
</code></pre>
<blockquote>
<p>关闭游标</p>
</blockquote>
<pre><code>CLOSE 游标名;
</code></pre>
<h4>条件处理程序</h4>
<p>定义在流程控制执行过程中遇到条件时的处理步骤。</p>
<pre><code>DECLARE handler_action HANDLER FOR condition_value,... SQL;
</code></pre>
<p>handler_action:</p>
<ul>
<li>EXIT 满足condition_value时退出</li>
<li>CONTINUE 满足condition_value时继续</li>
</ul>
<p>condition_value:</p>
<ul>
<li>SQLSTATE sqlstate_value:状态码，如SQLSTATE '02000'</li>
</ul>
<p>常用的条件类别：</p>
<ul>
<li><code>SQLWARNING</code>：SQLSTATE 类别 <code>01</code>。</li>
<li><code>NOT FOUND</code>：SQLSTATE 类别 <code>02</code>。</li>
<li><code>SQLEXCEPTION</code>：除成功、警告和未找到之外的异常类别。</li>
</ul>
<h3>存储函数</h3>
<p>存储函数通过 <code>RETURN</code> 返回值；函数参数默认都是输入参数，声明时不写 <code>IN</code>。</p>
<pre><code>CREATE FUNCTION add_one(n INT)
RETURNS INT DETERMINISTIC NO SQL
RETURN n + 1;
</code></pre>
<p>characteristic特性说明:</p>
<ul>
<li>DETERMINISTIC: 相同的输入参数总是产生相同的结果</li>
<li>NO SQL：不包含SQL语句</li>
<li>READS SQL DATA：包含查询数据的语句，但不包含写入数据的语句。</li>
</ul>
<p>启用二进制日志且 <code>log_bin_trust_function_creators=0</code> 时，创建函数可能要求声明 <code>DETERMINISTIC</code>、<code>NO SQL</code> 或 <code>READS SQL DATA</code>；应按函数的实际行为声明，不能为绕过错误而标注不真实的特性。</p>
<h3>触发器</h3>
<p>触发器与表关联，在插入、更新或删除每行之前或之后执行，可用于数据校验或记录变更。
使用别名OLD和NEW来引用触发器中发生变化的记录内容。支持行级触发，不支持语句级触发。</p>
<ol>
<li>INSERT型触发器</li>
</ol>
<ul>
<li>NEW表示将要或者已经新增的数据</li>
</ul>
<ol>
<li>UPDATE型触发器</li>
</ol>
<ul>
<li>OLD表示修改之前的数据，NEW表示将要或者已经修改后的数据</li>
</ul>
<ol>
<li>DELETE型触发器</li>
</ol>
<ul>
<li>OLD表示将要或者已经删除的数据</li>
</ul>
<h4>语法</h4>
<pre><code>CREATE TRIGGER 触发器名
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON 表名 FOR EACH ROW --行级触发器
BEGIN
    SQL;
END;
</code></pre>
<blockquote>
<p>查看</p>
</blockquote>
<pre><code>SHOW TRIGGERS;
</code></pre>
<blockquote>
<p>删除触发器</p>
</blockquote>
<pre><code>DROP TRIGGER [数据库名.]触发器名;
--如果没有指定数据库则默认为当前数据库
</code></pre>
<h3>参考资料</h3>
<ul>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-index-types.html">MySQL 8.4 手册：InnoDB 聚集索引与二级索引</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/optimizing-spatial-analysis.html">MySQL 8.4 手册：空间索引优化</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/explain-output.html">MySQL 8.4 手册：EXPLAIN 输出</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/show-profile.html">MySQL 8.4 手册：SHOW PROFILE</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/create-view.html">MySQL 8.4 手册：视图</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/create-procedure.html">MySQL 8.4 手册：存储过程与函数</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/create-trigger.html">MySQL 8.4 手册：触发器</a></li>
</ul>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Study"/>
  </entry>
  <entry>
    <title>AstrBot GitHub 控制器：在聊天中管理仓库</title>
    <link href="https://www.pardofelis.top/posts/3920e45f/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/3920e45f/</id>
    <published>2026-10-08T00:00:00.000Z</published>
    <updated>2026-10-08T00:00:00.000Z</updated>
    <summary>把 GitHub REST API 接入 AstrBot，以聊天指令和大模型工具处理常见的仓库工作。</summary>
    <content type="html"><![CDATA[<p>查看 Issue、合并 PR 或触发工作流时，我经常需要从聊天窗口切到 GitHub 页面。GitHub 控制器是为 AstrBot 写的插件，将这些常见操作接入聊天指令，也可以在启用后交给大模型工具调用。</p>
<h2>覆盖的操作</h2>
<ul>
<li>搜索、查看、创建、评论和关闭 Issue，并调整标签。</li>
<li>查看、创建和合并 Pull Request。</li>
<li>查询 GitHub Actions 的运行记录，或触发支持 <code>workflow_dispatch</code> 的工作流。</li>
<li>查看、创建和修改仓库，以及读取、创建或更新仓库文件。</li>
</ul>
<p>聊天中发送 <code>/gh</code> 可以查看指令树；例如 <code>/gh issues</code> 列出 Issue，<code>/gh runs</code> 查看工作流运行记录。插件还注册了 18 个大模型工具，让模型能够在对话中读取相关信息并执行已开放的操作。指令和工具使用同一套 GitHub REST API 请求层。</p>
<h2>配置与权限</h2>
<p>在 AstrBot 插件配置中填写 GitHub Token；可选地设置默认仓库，省去反复输入 <code>owner/name</code>。插件支持自定义 API 地址，可用于 GitHub Enterprise Server；也能关闭大模型工具，只保留聊天指令。</p>
<p>创建、修改、关闭、合并、触发工作流和写文件等操作默认仅允许 AstrBot 管理员执行。建议为 Token 只授予实际要使用的仓库和权限，并在执行写操作前核对目标对象。Token 来自插件配置，不需要写进文章或代码。</p>
<p>安装方式、完整指令表和所需 Token 权限见 <a href="https://github.com/syuan326/astrbot_plugin_github_controller">GitHub 控制器仓库</a>。</p>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>MySQL 基础学习</title>
    <link href="https://www.pardofelis.top/posts/4036ecba/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/4036ecba/</id>
    <published>2026-10-07T22:35:41.000Z</published>
    <updated>2026-10-09T10:06:16.000Z</updated>
    <summary>记录 MySQL 基础学习过程，整理核心概念与常用操作，便于复习和查阅。</summary>
    <content type="html"><![CDATA[<h3>DDL - 数据库与表操作</h3>
<p>以下尖括号表示需要替换的名称或值，方括号表示可选部分；执行前应删除占位符。</p>
<blockquote>
<p>建库</p>
</blockquote>
<pre><code>CREATE DATABASE &lt;DATABASE_NAME&gt;;
</code></pre>
<blockquote>
<p>使用库</p>
</blockquote>
<pre><code>USE &lt;DATABASE_NAME&gt;;
</code></pre>
<blockquote>
<p>查询当前数据库所有表</p>
</blockquote>
<pre><code>SHOW TABLES;
</code></pre>
<blockquote>
<p>查询表结构</p>
</blockquote>
<pre><code>DESC &lt;TABLE_NAME&gt;;
</code></pre>
<blockquote>
<p>查询指定表的建表语句</p>
</blockquote>
<pre><code>SHOW CREATE TABLE &lt;TABLE_NAME&gt;;
</code></pre>
<blockquote>
<p>创建表</p>
</blockquote>
<pre><code>CREATE TABLE IF NOT EXISTS &lt;TABLE_NAME&gt; (
    &lt;COLUMN_NAME&gt; &lt;DATA_TYPE&gt; [NOT NULL] [DEFAULT &lt;VALUE&gt;] [COMMENT '注释'],
    &lt;COLUMN_NAME&gt; &lt;DATA_TYPE&gt; [NOT NULL] [DEFAULT &lt;VALUE&gt;]
) COMMENT = '表注释';
</code></pre>
<blockquote>
<p>更新表字段(X)</p>
</blockquote>
<pre><code>-- 加列
ALTER TABLE &lt;TABLE_NAME&gt; ADD COLUMN &lt;COLUMN_NAME&gt; &lt;DATA_TYPE&gt; [COMMENT '注释'];
-- 改列类型
ALTER TABLE &lt;TABLE_NAME&gt; MODIFY COLUMN &lt;COLUMN_NAME&gt; &lt;DATA_TYPE&gt;;
-- 改列名
ALTER TABLE &lt;TABLE_NAME&gt; CHANGE COLUMN &lt;OLD_NAME&gt; &lt;NEW_NAME&gt; &lt;DATA_TYPE&gt;;
-- 删列
ALTER TABLE &lt;TABLE_NAME&gt; DROP COLUMN &lt;COLUMN_NAME&gt;;
</code></pre>
<blockquote>
<p>删除表</p>
</blockquote>
<pre><code>DROP TABLE IF EXISTS &lt;TABLE_NAME&gt;;
</code></pre>
<blockquote>
<p>删除重建表(X)</p>
</blockquote>
<pre><code>TRUNCATE TABLE &lt;TABLE_NAME&gt;;
</code></pre>
<blockquote>
<p>重命名表(X)</p>
</blockquote>
<pre><code>RENAME TABLE &lt;OLD_TABLE_NAME&gt; TO &lt;NEW_TABLE_NAME&gt;;
</code></pre>
<blockquote>
<p>删除数据库</p>
</blockquote>
<pre><code>DROP DATABASE &lt;DATABASE_NAME&gt;;
</code></pre>
<blockquote>
<p>加索引</p>
</blockquote>
<pre><code>CREATE INDEX &lt;INDEX_NAME&gt; ON &lt;TABLE_NAME&gt;(&lt;COLUMN_NAME&gt;);
</code></pre>
<blockquote>
<p>删索引</p>
</blockquote>
<pre><code>DROP INDEX &lt;INDEX_NAME&gt; ON &lt;TABLE_NAME&gt;;
</code></pre>
<h4>数据类型</h4>
<blockquote>
<p>数值类型</p>
</blockquote>
<ol>
<li>
<p>TINYINT</p>
<ol>
<li>大小：1 byte</li>
<li>有符号范围：（-128，127）</li>
<li>无符号范围（UNSIGNED）：（0，255）</li>
<li>描述：小整数值</li>
</ol>
</li>
<li>
<p>SMALLINT</p>
<ol>
<li>大小：2 bytes</li>
<li>有符号范围：（-32768，32767）</li>
<li>无符号范围（UNSIGNED）：（0，65535）</li>
<li>描述：大整数值</li>
</ol>
</li>
<li>
<p>MEDIUMINT</p>
<ol>
<li>大小：3 bytes</li>
<li>有符号范围：（-8388608，8388607）</li>
<li>无符号范围（UNSIGNED）：（0，16777215）</li>
<li>描述：大整数值</li>
</ol>
</li>
<li>
<p>INT/INTEGER</p>
<ol>
<li>大小：4 bytes</li>
<li>有符号范围：（-2147483648，2147483647）</li>
<li>无符号范围（UNSIGNED）：（0，4294967295）</li>
<li>描述：大整数值</li>
</ol>
</li>
<li>
<p>BIGINT</p>
<ol>
<li>大小：8 bytes</li>
<li>有符号范围：（-2^63，2^63-1）</li>
<li>无符号范围（UNSIGNED）：（0，2^64-1）</li>
<li>描述：极大整数值</li>
</ol>
</li>
<li>
<p>FLOAT</p>
<ol>
<li>大小：4 bytes</li>
<li>描述：单精度近似数，精度约 7 位十进制数字</li>
<li><code>UNSIGNED</code> 属性已弃用，需要非负值时优先使用 <code>CHECK</code> 约束</li>
</ol>
</li>
<li>
<p>DOUBLE</p>
<ol>
<li>大小：8 bytes</li>
<li>描述：双精度近似数，精度约 15 位十进制数字</li>
<li><code>UNSIGNED</code> 属性已弃用，需要非负值时优先使用 <code>CHECK</code> 约束</li>
</ol>
</li>
<li>
<p>DECIMAL</p>
<ol>
<li>有符号范围：依赖于M（精度）和D（标度）的值</li>
<li><code>UNSIGNED</code> 属性已弃用，需要非负值时优先使用 <code>CHECK</code> 约束</li>
<li>例子：12.3-&gt;M=3, D=1</li>
<li>描述：小数值（精确定点数）</li>
</ol>
</li>
</ol>
<blockquote>
<p>字符串类型</p>
</blockquote>
<ol>
<li>CHAR
<ol>
<li>长度：0-255 个字符（实际字节数取决于字符集）</li>
<li>描述：定长字符串</li>
</ol>
</li>
<li>VARCHAR
<ol>
<li>长度：最多 65,535 个字符；实际最大长度受字符集和行大小限制</li>
<li>描述：不定长字符串</li>
</ol>
</li>
<li>TINYBLOB
<ol>
<li>大小：最多 255 bytes</li>
<li>描述：不超过 255 字节的二进制数据</li>
</ol>
</li>
<li>TINYTEXT
<ol>
<li>大小：最多 255 bytes</li>
<li>描述：短文本数据</li>
</ol>
</li>
<li>BLOB
<ol>
<li>大小：最多 65,535 bytes</li>
<li>描述：二进制数据</li>
</ol>
</li>
<li>TEXT
<ol>
<li>大小：最多 65,535 bytes</li>
<li>描述：长文本数据</li>
</ol>
</li>
<li>MEDIUMBLOB
<ol>
<li>大小：最多 16,777,215 bytes</li>
<li>描述：中等长度的二进制数据</li>
</ol>
</li>
<li>MEDIUMTEXT
<ol>
<li>大小：最多 16,777,215 bytes</li>
<li>描述：中等长度文本数据</li>
</ol>
</li>
<li>LONGBLOB
<ol>
<li>大小：最多 4,294,967,295 bytes</li>
<li>描述：大容量二进制数据</li>
</ol>
</li>
<li>LONGTEXT</li>
<li>大小：最多 4,294,967,295 bytes
2. 描述：极大文本数据</li>
</ol>
<blockquote>
<p>日期时间类型</p>
</blockquote>
<ol>
<li>DATE
<ol>
<li>大小：3</li>
<li>范围：1000-01-01 ~ 9999-12-31</li>
<li>格式：YYYY-MM-DD</li>
<li>描述：日期值</li>
</ol>
</li>
<li>TIME
<ol>
<li>大小：3</li>
<li>范围：-838:59:59 ~ 838:59:59</li>
<li>格式：HH:MM:SS</li>
<li>描述：时间值或持续时间</li>
</ol>
</li>
<li>YEAR
<ol>
<li>大小：1</li>
<li>范围：1901 ~ 2155</li>
<li>格式：YYYY</li>
<li>描述：年份值</li>
</ol>
</li>
<li>DATETIME
<ol>
<li>大小：8</li>
<li>范围：1000-01-01 00:00:00 ~ 9999-12-31 23:59:59</li>
<li>格式：YYYY-MM-DD HH:MM:SS</li>
<li>描述：混合日期值和时间值</li>
</ol>
</li>
<li>TIMESTAMP
<ol>
<li>大小：4</li>
<li>范围：1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC；读写时按会话时区转换</li>
<li>格式：YYYY-MM-DD HH:MM:SS</li>
<li>描述：时间戳</li>
</ol>
</li>
</ol>
<h3>DML - 数据操作</h3>
<blockquote>
<p>新增</p>
</blockquote>
<pre><code>INSERT INTO &lt;TABLE_NAME&gt; (&lt;COLUMN_1&gt;, &lt;COLUMN_2&gt;) VALUES (&lt;VALUE_1&gt;, &lt;VALUE_2&gt;);
/*
插入数据时，需要保持值与列对应；需要保证值符合列的限制；字符串和日期类型数据应该包含在引号中
*/
</code></pre>
<blockquote>
<p>更新(X)</p>
</blockquote>
<pre><code>UPDATE &lt;TABLE_NAME&gt; SET &lt;COLUMN_NAME&gt; = &lt;VALUE&gt; WHERE &lt;CONDITION&gt;;
</code></pre>
<blockquote>
<p>删除(X)</p>
</blockquote>
<pre><code>DELETE FROM &lt;TABLE_NAME&gt; WHERE &lt;CONDITION&gt;;
</code></pre>
<blockquote>
<p>替换：主键或唯一键冲突时先删后插</p>
</blockquote>
<pre><code>REPLACE INTO &lt;TABLE_NAME&gt; (&lt;COLUMN_1&gt;, &lt;COLUMN_2&gt;) VALUES (&lt;VALUE_1&gt;, &lt;VALUE_2&gt;);
</code></pre>
<blockquote>
<p>插入冲突时更新</p>
</blockquote>
<pre><code>INSERT INTO &lt;TABLE_NAME&gt; (&lt;COLUMN_1&gt;, &lt;COLUMN_2&gt;) VALUES (&lt;VALUE_1&gt;, &lt;VALUE_2&gt;) AS new
ON DUPLICATE KEY UPDATE &lt;COLUMN_2&gt; = new.&lt;COLUMN_2&gt;;
</code></pre>
<h3>DQL - 数据查找</h3>
<blockquote>
<p>查询</p>
</blockquote>
<pre><code>SELECT &lt;COLUMN_1&gt;, &lt;COLUMN_2&gt; FROM &lt;TABLE_NAME&gt;
[WHERE &lt;CONDITION&gt;] [GROUP BY &lt;COLUMN_1&gt;] [HAVING &lt;GROUP_CONDITION&gt;]
[ORDER BY &lt;COLUMN_1&gt;] [LIMIT &lt;OFFSET&gt;, &lt;PAGE_SIZE&gt;];
</code></pre>
<blockquote>
<p>设置别名</p>
</blockquote>
<pre><code>SELECT &lt;COLUMN&gt; [AS 别名] FROM &lt;TABLE_NAME&gt;;
</code></pre>
<blockquote>
<p>去重</p>
</blockquote>
<pre><code>SELECT DISTINCT &lt;COLUMN&gt; FROM &lt;TABLE_NAME&gt;;
</code></pre>
<h4>聚合函数</h4>
<table>
<thead>
<tr>
<th>函数</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>count</td>
<td>统计数量</td>
</tr>
<tr>
<td>max</td>
<td>最大值</td>
</tr>
<tr>
<td>min</td>
<td>最小值</td>
</tr>
<tr>
<td>avg</td>
<td>平均数</td>
</tr>
<tr>
<td>sum</td>
<td>总计</td>
</tr>
</tbody>
</table>
<blockquote>
<p>语法</p>
</blockquote>
<pre><code>SELECT COUNT(*) FROM &lt;TABLE_NAME&gt;;
-- COUNT(*) 统计所有行；COUNT(列名)、SUM、AVG 等忽略 NULL 值

-- 分组与分组后过滤
SELECT &lt;GROUP_COLUMN&gt;, COUNT(*) FROM &lt;TABLE_NAME&gt; [WHERE &lt;CONDITION&gt;] GROUP BY &lt;GROUP_COLUMN&gt; [HAVING &lt;GROUP_CONDITION&gt;];

-- 排序（升序asc,降序desc,默认为升序）
SELECT &lt;COLUMN&gt; FROM &lt;TABLE_NAME&gt; ORDER BY &lt;COLUMN&gt; [ASC | DESC];
-- 当存在多个排序条件时，当前一个字段值相同时，才会根据第二个字段进行排序

-- 分页
SELECT &lt;COLUMN&gt; FROM &lt;TABLE_NAME&gt; LIMIT &lt;OFFSET&gt;, &lt;PAGE_SIZE&gt;;
-- 起始索引一般为(查询页码-1)页x每页显示记录数量，MYSQL的方言是LIMIT，如果查询的是第一页，起始索引(0)可以省略

</code></pre>
<p>WHERE &amp; HAVING的区别</p>
<ul>
<li>执行时机：WHERE要先于聚合，HAVING后于聚合</li>
<li>判断条件：WHERE不能对聚合函数进行判断，HAVING可以</li>
</ul>
<h4>执行顺序</h4>
<pre><code>FROM
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
LIMIT
</code></pre>
<h3>DCL - 用户与权限</h3>
<h4>用户管理</h4>
<blockquote>
<p>查询用户</p>
</blockquote>
<pre><code>USE mysql;
SELECT User, Host FROM mysql.user;
</code></pre>
<blockquote>
<p>创建用户</p>
</blockquote>
<pre><code>CREATE USER 'USERNAME'@'HOST' IDENTIFIED BY 'PASSWORD';
</code></pre>
<blockquote>
<p>修改用户密码</p>
</blockquote>
<pre><code>ALTER USER 'USERNAME'@'HOST' IDENTIFIED BY 'NEWPASSWORD';
</code></pre>
<blockquote>
<p>删除用户</p>
</blockquote>
<pre><code>DROP USER 'USERNAME'@'HOST';
</code></pre>
<h4>权限管理</h4>
<table>
<thead>
<tr>
<th>权限</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>ALL/ ALL PRIVILEGES</td>
<td>全部权限</td>
</tr>
<tr>
<td>SELECT</td>
<td>查询</td>
</tr>
<tr>
<td>INSERT</td>
<td>插入</td>
</tr>
<tr>
<td>UPDATE</td>
<td>更新</td>
</tr>
<tr>
<td>DELETE</td>
<td>删除</td>
</tr>
<tr>
<td>ALTER</td>
<td>修改表</td>
</tr>
<tr>
<td>DROP</td>
<td>删除表、数据库、视图</td>
</tr>
<tr>
<td>CREATE</td>
<td>创建数据库、表</td>
</tr>
</tbody>
</table>
<blockquote>
<p>查询权限</p>
</blockquote>
<pre><code>SHOW GRANTS FOR 'USERNAME'@'HOST';
</code></pre>
<blockquote>
<p>授予权限</p>
</blockquote>
<pre><code>GRANT &lt;PRIVILEGE&gt; ON &lt;DATABASE_NAME&gt;.&lt;TABLE_NAME&gt; TO 'USERNAME'@'HOST';
</code></pre>
<blockquote>
<p>撤销权限</p>
</blockquote>
<pre><code>REVOKE &lt;PRIVILEGE&gt; ON &lt;DATABASE_NAME&gt;.&lt;TABLE_NAME&gt; FROM 'USERNAME'@'HOST';
</code></pre>
<p>多个权限之间可以用逗号隔开
授权时，数据库和表名可以用*代替，代表所有</p>
<h3>内置函数</h3>
<h4>字符串</h4>
<table>
<thead>
<tr>
<th>函数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>CONCAT(S1, S2,...)</td>
<td>拼接字符串</td>
</tr>
<tr>
<td>LOWER(STR)</td>
<td>转小写</td>
</tr>
<tr>
<td>UPPER(STR)</td>
<td>转大写</td>
</tr>
<tr>
<td>LPAD(STR,N,PAD)</td>
<td>用PAD左填充字符直到字符串长度为N</td>
</tr>
<tr>
<td>RPAD(STR,N,PAD)</td>
<td>用 PAD 右填充字符直到字符串长度为 N</td>
</tr>
<tr>
<td>TRIM(STR)</td>
<td>清除字符串前后空格</td>
</tr>
<tr>
<td>SUBSTRING(STR,START,LEN)</td>
<td>取出第START及以后LEN个字符作为子字符串</td>
</tr>
</tbody>
</table>
<p>语法：SELECT FUN(参数);</p>
<h4>数值函数</h4>
<table>
<thead>
<tr>
<th>函数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>CEIL(X)</td>
<td>向上取整</td>
</tr>
<tr>
<td>FLOOR(X)</td>
<td>向下取整</td>
</tr>
<tr>
<td>MOD(X,Y)</td>
<td>取余</td>
</tr>
<tr>
<td>RAND()</td>
<td>0~1随机数</td>
</tr>
<tr>
<td>ROUND(X,Y)</td>
<td>四舍五入X是原数，Y是保留位数</td>
</tr>
</tbody>
</table>
<h4>日期函数</h4>
<table>
<thead>
<tr>
<th>函数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>CURDATE()</td>
<td>当前日期</td>
</tr>
<tr>
<td>CURTIME()</td>
<td>当前时间</td>
</tr>
<tr>
<td>NOW()</td>
<td>当前日期时间</td>
</tr>
<tr>
<td>YEAR(DATE)</td>
<td>日期的年</td>
</tr>
<tr>
<td>MONTH(DATE)</td>
<td>日期中的月</td>
</tr>
<tr>
<td>DAY(DATE)</td>
<td>日期中的天</td>
</tr>
<tr>
<td>DATE_ADD(DATE, INTERVAL EXPR UNIT)</td>
<td>在日期上加 EXPR 个时间单位</td>
</tr>
<tr>
<td>DATEDIFF(DATE1, DATE2)</td>
<td>返回 DATE1 与 DATE2 相差的天数</td>
</tr>
</tbody>
</table>
<h4>流程函数</h4>
<table>
<thead>
<tr>
<th>函数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>IF(VALUE, T,F)</td>
<td>VALUE为真就返回T，否则返回F</td>
</tr>
<tr>
<td>IFNULL(VALUE1, VALUE2)</td>
<td>VALUE1为空就返回VALUE2，否则返回VALUE1</td>
</tr>
<tr>
<td>CASE WHEN COND1 THEN RES1 WHEN COND2 THEN RES2 ELSE RESN END</td>
<td>返回第一个满足条件的结果</td>
</tr>
<tr>
<td>CASE EXPR WHEN VAL1 THEN RES1 ELSE RESN END</td>
<td>返回与 EXPR 匹配的结果</td>
</tr>
</tbody>
</table>
<h3>约束</h3>
<p>概念：作用于表中的字段，限制表中的数据</p>
<table>
<thead>
<tr>
<th>约束</th>
<th>描述</th>
<th>关键字</th>
</tr>
</thead>
<tbody>
<tr>
<td>非空约束</td>
<td>限制该字段必填</td>
<td>NOT NULL</td>
</tr>
<tr>
<td>唯一约束</td>
<td>限制非 <code>NULL</code> 值重复；允许 <code>NULL</code> 时可有多个 <code>NULL</code></td>
<td>UNIQUE</td>
</tr>
<tr>
<td>主键约束</td>
<td>记录的唯一标识，非空且唯一</td>
<td>PRIMARY KEY</td>
</tr>
<tr>
<td>默认约束</td>
<td>插入时未指定该字段则使用默认值</td>
<td>DEFAULT</td>
</tr>
<tr>
<td>检查约束</td>
<td>字段值必须满足条件</td>
<td>CHECK</td>
</tr>
<tr>
<td>外键约束</td>
<td>必须严格根据父表参考字段进行输入</td>
<td>FOREIGN KEY</td>
</tr>
</tbody>
</table>
<p>主键约束自增：AUTO_INCREMENT</p>
<h4>外键约束</h4>
<p>让两个表产生联系，保证数据的一致性和完整性
拥有外键的叫子表，被参考的叫父表</p>
<p>父表更新、删除行为：</p>
<table>
<thead>
<tr>
<th>行为</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>NO ACTION</td>
<td>父表有外键则不允许删除、更新</td>
</tr>
<tr>
<td>RESTRICT</td>
<td>与NO ACTION一致</td>
</tr>
<tr>
<td>CASCADE</td>
<td>级联删除、更新</td>
</tr>
<tr>
<td>SET NULL</td>
<td>父表删除时子表外键置空</td>
</tr>
<tr>
<td>SET DEFAULT</td>
<td>父表删除时使用默认值（InnoDB 不支持）</td>
</tr>
</tbody>
</table>
<p>语法：</p>
<pre><code>ALTER TABLE &lt;CHILD_TABLE&gt; ADD CONSTRAINT &lt;FK_NAME&gt;
FOREIGN KEY (&lt;CHILD_COLUMN&gt;) REFERENCES &lt;PARENT_TABLE&gt; (&lt;PARENT_COLUMN&gt;)
ON UPDATE CASCADE ON DELETE CASCADE;
</code></pre>
<h4>多表关系</h4>
<blockquote>
<p>一对多
在多的一方建立外键，指向一的一方的主键</p>
</blockquote>
<blockquote>
<p>多对多
需要有中间表，中间表至少包含两个外键，分别关联两方主键</p>
</blockquote>
<blockquote>
<p>一对一
在任意一方加入外键，关联另一张表的主键，并且设置外键为唯一（UNIQUE）的</p>
</blockquote>
<h3>查询</h3>
<h4>内连接</h4>
<blockquote>
<p>隐式内连接</p>
</blockquote>
<pre><code>SELECT &lt;COLUMN&gt; FROM &lt;TABLE1&gt;, &lt;TABLE2&gt;, ... WHERE &lt;OPTION&gt;;
</code></pre>
<blockquote>
<p>显式内连接</p>
</blockquote>
<pre><code>SELECT &lt;COLUMN&gt; FROM &lt;TABLE1&gt; INNER JOIN &lt;TABLE2&gt; ON &lt;CONDITION&gt;;
</code></pre>
<p>一般需要条件,否则会导致完整的笛卡儿积被查询
内连接查询的是两张表的交集部分</p>
<h4>外连接</h4>
<blockquote>
<p>左外连接</p>
</blockquote>
<pre><code>SELECT &lt;COLUMN&gt; FROM &lt;TABLE1&gt; LEFT JOIN &lt;TABLE2&gt; ON &lt;CONDITION&gt;;
</code></pre>
<blockquote>
<p>右外连接</p>
</blockquote>
<pre><code>SELECT &lt;COLUMN&gt; FROM &lt;TABLE&gt; RIGHT JOIN &lt;TABLE2&gt; ON &lt;OPTION&gt;;
</code></pre>
<p>左外连接，需要完整包含左表（第一张表）的所有记录，其查询结果为左表的所有记录加上右表中左右表的交集部分
右外连接保留右表（第二张表）的所有记录，并匹配左表中的对应记录；没有匹配时左表列为 <code>NULL</code>。</p>
<h4>自连接</h4>
<pre><code>SELECT &lt;COLUMN&gt; FROM &lt;TABLE&gt; AS &lt;别名&gt; JOIN &lt;TABLE&gt; AS &lt;别名&gt; ON &lt;OPTION&gt;;
</code></pre>
<p>自连接查询可以是内连接也可以是外连接</p>
<h4>UNION联合查询</h4>
<p>就是把多次查询的结果合并起来，形成新的查询结果集
UNION 时表示去重合并
UNION ALL时表示不去重合并</p>
<pre><code>SELECT &lt;COLUMN&gt; FROM &lt;TABLE&gt;...
UNION
SELECT &lt;COLUMN&gt; FROM &lt;TABLE&gt;...;
</code></pre>
<h4>子查询</h4>
<p>SQL语句中嵌套SELECT语句，称为嵌套查询/子查询</p>
<pre><code>SELECT * FROM T1 WHERE COLUMN1 = (SELECT COLUMN1 FROM T2);
</code></pre>
<p>根据子查询结果不同，分为：</p>
<ul>
<li>标量子查询（子查询结果为单个值）
<ul>
<li>返回的结果是单个值（数字、字符串、日期等）。常用的操作符：= &lt;&gt; &gt; &gt;= &lt; &lt;=</li>
</ul>
</li>
<li>列子查询（子查询结果为一列多个值）
<ul>
<li>常用操作符
<ul>
<li>IN在指定的集合范围之内多选一</li>
<li>NOT IN不在指定的集合范围内</li>
<li>ANY任意一个满足即可</li>
<li>SOME与ANY等价</li>
<li>ALL返回列表的所有值都必须满足</li>
</ul>
</li>
</ul>
</li>
<li>行子查询（子查询结果为一行一个记录）
<ul>
<li>常用操作符：=、&lt;&gt;、IN、NOT IN</li>
</ul>
</li>
<li>表子查询（子查询结果为多行多列）
<ul>
<li>常放在 <code>FROM</code> 中作为派生表；多列 <code>IN</code> 比较需要对应的行构造器。</li>
</ul>
</li>
</ul>
<p>根据子查询位置，分为：WHERE之后、FROM之后、SELECT之后</p>
<h3>事务</h3>
<blockquote>
<p>开启事务</p>
</blockquote>
<pre><code>START TRANSACTION;
BEGIN;
</code></pre>
<blockquote>
<p>提交事务</p>
</blockquote>
<pre><code>COMMIT;
</code></pre>
<blockquote>
<p>回滚事务</p>
</blockquote>
<pre><code>ROLLBACK;
</code></pre>
<h4>事务四大特性</h4>
<ul>
<li>原子性：事务是不可分割的最小单元，要么全部成功要么全部失败</li>
<li>一致性：事务完成时，必须要使得所有数据保持一致状态</li>
<li>隔离性：事务会在不受外部并发操作的影响下运行</li>
<li>持久性：事务一旦提交，其修改会持久保存；回滚不会保存该事务的修改</li>
</ul>
<h4>并发事务问题</h4>
<ul>
<li>脏读：读到了未提交的数据</li>
<li>不可重复读：一个事务先后读取同一个数据，两次读到的数据不同</li>
<li>幻读：同一事务按相同条件重复查询时，由于其他事务插入或删除了符合条件的行，结果集的行数发生变化</li>
</ul>
<h4>隔离机制</h4>
<table>
<thead>
<tr>
<th>隔离级别</th>
<th>脏读</th>
<th>不可重复读</th>
<th>幻读</th>
</tr>
</thead>
<tbody>
<tr>
<td>Read uncommitted</td>
<td>Y</td>
<td>Y</td>
<td>Y</td>
</tr>
<tr>
<td>Read committed</td>
<td>N</td>
<td>Y</td>
<td>Y</td>
</tr>
<tr>
<td>Repeatable Read（InnoDB 默认）</td>
<td>N</td>
<td>N</td>
<td>视查询方式而定*</td>
</tr>
<tr>
<td>Serializable</td>
<td>N</td>
<td>N</td>
<td>N</td>
</tr>
</tbody>
</table>
<p>*InnoDB 的一致性读在同一事务中使用快照；锁定读和写操作还会使用锁控制并发，因此不能只用一格“是/否”概括幻读行为。</p>
<blockquote>
<p>查看事务隔离级别</p>
</blockquote>
<pre><code>SELECT @@transaction_isolation;
</code></pre>
<blockquote>
<p>设置事务隔离级别</p>
</blockquote>
<pre><code>SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL READ COMMITTED;
</code></pre>
<h3>存储引擎</h3>
<p>存储引擎基于表，所以存储引擎也被成为表类型</p>
<blockquote>
<p>在创建表时指定存储引擎</p>
</blockquote>
<pre><code>CREATE TABLE &lt;TABLE_NAME&gt; (
   ...,
   ...,
   ...
) ENGINE = InnoDB [COMMENT = '表注释'];
</code></pre>
<blockquote>
<p>查看当前数据库支持的引擎</p>
</blockquote>
<pre><code>SHOW ENGINES;
</code></pre>
<h4>InnoDB</h4>
<p>是MySQL默认的MySQL引擎
特点：
DML操作遵循ACID模型，支持事务
行级锁，提高并发访问能力
支持外键FOREIGN KEY约束，保证数据的完整性和正确性</p>
<p>文件：xxx.ibd
启用独立表空间时，表通常对应一个 <code>.ibd</code> 文件，保存表数据和索引；数据字典由 MySQL 管理。
参数：innodb_file_per_table</p>
<p>逻辑存储结构
TableSpace：表空间，包含多个Segment
Segment：段，包含多个区
Extent：区，包含多个页，大小为1M
Page：页，包含多行，为数据表主力，大小为16K
Row：行，包含各种字段</p>
<h4>MyISAM</h4>
<p>是MySQL早期默认引擎
特点：
不支持事务，不支持外键
支持表锁，不支持行锁
访问速度快</p>
<h4>MEMORY</h4>
<p>表数据存储在内存中。由于受到硬件问题、或者断电问题的影响，只能作为临时表或缓存使用。
特点：
内存存放
hash索引（默认）
文件：
xxx.sdi：存储表结构信息</p>
<h4>存储引擎特点</h4>
<table>
<thead>
<tr>
<th>特点</th>
<th>InnoDB</th>
<th>MyISAM</th>
<th>Memory</th>
</tr>
</thead>
<tbody>
<tr>
<td>存储限制</td>
<td>64TB</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>B+树索引</td>
<td>支持</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>Hash索引</td>
<td>-</td>
<td>-</td>
<td>支持</td>
</tr>
<tr>
<td>全文索引</td>
<td>支持（5.6版本之后）</td>
<td>支持</td>
<td>-</td>
</tr>
<tr>
<td>空间时间</td>
<td>高</td>
<td>低</td>
<td>N/A</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>
</tbody>
</table>
<h3>参考资料</h3>
<ul>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/create-table.html">MySQL 8.4 手册：CREATE TABLE</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html">MySQL 8.4 手册：数值类型语法</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/char.html">MySQL 8.4 手册：CHAR 与 VARCHAR</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/datetime.html">MySQL 8.4 手册：日期与时间类型</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html">MySQL 8.4 手册：InnoDB 事务隔离级别</a></li>
</ul>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Study"/>
  </entry>
  <entry>
    <title>COS 定时备份：为 AstrBot 增加多路径备份</title>
    <link href="https://www.pardofelis.top/posts/62e9fdc3/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/62e9fdc3/</id>
    <published>2026-10-06T02:10:00.000Z</published>
    <updated>2026-10-06T02:10:00.000Z</updated>
    <summary>将不同本地路径分别打包为 ZIP，上传到腾讯云 COS，并支持查询与下载。</summary>
    <content type="html"><![CDATA[<p>COS 定时备份是一个 AstrBot 插件，用来把多条本地文件或目录分别压缩为 ZIP，再上传到腾讯云 COS，包括轻量对象存储版本。每条任务可以指定自己的本地路径和云端目录，适合将不同服务的数据分开管理。</p>
<h2>备份流程</h2>
<p>插件既支持手动执行，也支持配置五段 Cron 定时运行。每轮按配置顺序处理任务，单个任务失败不会阻止其余任务；手动与定时备份共用锁，避免同一实例内的重叠执行。云端文件按日期和编号存放，编号从已有备份中确定，重启后仍能继续递增。</p>
<p>管理员可以通过指令启动全部或指定任务、查询历史备份、查看状态，并把选定 ZIP 下载到服务器。下载完成前会校验文件大小和 ZIP CRC；插件不会自动解压，也不会覆盖原始数据。</p>
<p>实现上，插件通过异步 HTTP/XML API 与 COS 通信，使用 COS v5 请求签名，不依赖腾讯云 SDK。较大的压缩包使用分块上传；压缩及批量文件读写放在工作线程中。</p>
<p>首次使用时，建议先备份测试目录，核对云端对象和下载后的 ZIP，再开启定时任务。运行中的数据库应先导出或停写，因为普通文件压缩不等同于数据库一致性快照。配置说明和指令列表见 <a href="https://github.com/syuan326/astrbot_plugin_cos_backup">COS 定时备份仓库</a>。</p>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>WW Login：XutheringWavesUID 的外置登录服务</title>
    <link href="https://www.pardofelis.top/posts/70b6cacf/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/70b6cacf/</id>
    <published>2026-10-06T02:05:00.000Z</published>
    <updated>2026-10-06T02:05:00.000Z</updated>
    <summary>xwuid的外置登录服务。</summary>
    <content type="html"><![CDATA[<p>WW Login 把 XutheringWavesUID 的登录页面从 GsCore 中拆出来，作为单独的 Web 服务运行。用户在聊天中发起登录后，GsCore 向服务申请链接；用户打开页面完成操作，GsCore 再取回登录结果。浏览器只需访问这个服务，不直接接触 GsCore 的端口。</p>
<h2>提供的页面与方式</h2>
<ul>
<li>手机号与验证码登录。</li>
<li>添加库街区 token，以及邮箱密码登录。</li>
<li>可选的云登录与抽卡记录登录；这部分需要匹配运行环境的 <code>cloud_api</code> 组件。</li>
<li>抽卡记录网页查看：GsCore 上传数据后，由独立服务展示页面。</li>
</ul>
<p>服务使用 FastAPI、Jinja2 等 Python 组件。登录链接有有效期，公开部署时建议使用 HTTPS 反向代理，并让 GsCore 只监听本机地址。<code>PUBLIC_URL</code> 需要与 GsCore 中配置的登录地址一致。</p>
<p>项目复用了 <a href="https://github.com/Loping151/XutheringWavesUID">XutheringWavesUID</a> 的登录页面、相关登录逻辑与云登录组件，并按原项目许可证使用。部署步骤、GsCore 配置示例和组件要求见 <a href="https://github.com/syuan326/ww-login">WW Login 仓库</a>。</p>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>AstrBot 腾讯云 DNS 解析控制器</title>
    <link href="https://www.pardofelis.top/posts/df05a483/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/df05a483/</id>
    <published>2026-10-06T02:00:00.000Z</published>
    <updated>2026-10-06T02:00:00.000Z</updated>
    <summary>通过 AstrBot 管理 DNSPod 解析记录，让日常域名配置跟上服务部署的节奏。</summary>
    <content type="html"><![CDATA[<p>部署一个新的 Web 服务时，反向代理配置好之后，往往还要打开 DNSPod 控制台新增解析记录。我写了这个 AstrBot 插件，把常用的 DNS 操作接到机器人里，减少在多个管理界面之间切换。</p>
<h2>能做什么</h2>
<ul>
<li>使用管理员指令列出当前域名的解析记录，并按记录 ID 删除记录。</li>
<li>指定子域名、记录类型和记录值，新增 DNSPod 解析记录。</li>
<li>向 AstrBot 的 LLM 提供查询、新增、删除解析记录的工具；管理员可以在配置中关闭这组工具，保留指令操作。</li>
</ul>
<p>插件通过 <code>aiohttp</code> 调用腾讯云 DNSPod API。它只管理插件配置中指定的域名，调用前需要填写腾讯云 API 密钥和域名。机器人指令与 LLM 工具均限制为管理员使用。</p>
<p>这个项目的目标是处理日常解析调整，尤其适合需要频繁为自托管服务添加子域名的场景。涉及删除记录时，先查询记录并核对 ID，再执行操作。</p>
<p>项目源码位于<a href="https://github.com/syuan326/astrbot_plugin_tencent_cloud_controller">腾讯云 DNS 解析控制器仓库</a>。</p>
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>基于Linux/Ubuntu的部署QQ鸣潮机器人的教程</title>
    <link href="https://www.pardofelis.top/posts/3c74c0f2/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/3c74c0f2/</id>
    <published>2026-03-15T00:44:07.000Z</published>
    <updated>2026-04-18T06:35:41.000Z</updated>
    <summary>本文是使用Ubuntu部署QQ鸣潮机器人的详细教程，涵盖GsCore、NapCatQQ及AstrBot等机器人框架。教程利用AstrBot通过NapCat连接个人QQ。本文内容包括各框架的安装步骤、环境准备、连接配置、token申请等方法。</summary>
    <content type="html"><![CDATA[<h2>各框架的官方网站</h2>
<p><a href="https://napcat.napneko.icu/">NapCat</a></p>
<hr />
<p><a href="https://astrbot.app/">AstrBot</a></p>
<hr />
<p><a href="https://docs.sayu-bot.com/">早柚核心</a></p>
<hr />
<p><a href="https://github.com/Loping151/XutheringWavesUID">XutheringWavesUID</a></p>
<hr />
<p><a href="https://github.com/Loping151/RoverSign">RoverSign</a></p>
<hr />
<h2>前期准备工作</h2>
<p>Linux/Ubuntu设备、闲置QQ号</p>
<h3>Linux环境检查</h3>
<h4>检查与安装Git</h4>
<pre><code># 检查 Git 版本
git --version

# 如果显示 "command not found"，请执行安装命令：
sudo apt update &amp;&amp; sudo apt install git -y
</code></pre>
<h4>检查与安装Docker(可选)</h4>
<pre><code># 检查 Docker 版本
docker --version
</code></pre>
<p>如果没有安装Docker，需要进行安装</p>
<pre><code># 更新索引并安装Ubuntu自带的docker包
sudo apt update
sudo apt install docker.io docker-compose-v2 -y
</code></pre>
<h4>检查环境与Github.com的网络连通性</h4>
<pre><code>ping github.com
</code></pre>
<p>如果 ping通了,延迟在100-300ms左右均属正常，可以继续。
如果显示Time out或Name or service not known：说明你的服务器无法解析或连接到 GitHub，直接clone源码大概率会失败</p>
<hr />
<h2>鸣潮机器人框架主体部署</h2>
<h3>GsCore早柚核心框架部署</h3>
<p>:::tabs</p>
<p>@tab docker部署</p>
<p><strong>一键部署并启动容器</strong></p>
<pre><code>docker run -d \
  --name gsuid_core \
  --restart always \
  -p 8765:8765 \
  -v /opt/gscore_data:/gsuid_core/data \
  -v /opt/gscore_plugins:/gsuid_core/gsuid_core/plugins \
  -v gsuid_core_venv:/venv \
  docker.cnb.cool/gscore-mirror/gsuid_core:latest
</code></pre>
<p>如果你需要自定义 <code>docker run</code> 命令，可以参考下表修改对应参数：</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>功能说明</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-d</code></td>
<td><strong>后台运行</strong></td>
<td>容器将在后台持续执行，不会占用当前终端窗口。</td>
</tr>
<tr>
<td><code>--name gsuid_core</code></td>
<td><strong>容器命名</strong></td>
<td>为容器指定一个名字，方便后续执行 <code>docker stop</code> 或 <code>logs</code> 命令。</td>
</tr>
<tr>
<td><code>-p 8765:8765</code></td>
<td><strong>端口映射</strong></td>
<td><code>宿主机端口:容器端口</code>。映射后通过 <code>IP:8765</code> 访问后台。</td>
</tr>
<tr>
<td><code>-v /opt/gscore_data:/gsuid_core/data</code></td>
<td><strong>数据持久化</strong></td>
<td><strong>极其重要</strong>！存放玩家账号和数据库，防止容器删除后数据丢失。</td>
</tr>
<tr>
<td><code>-v /opt/gscore_plugins:/gsuid_core/.../plugins</code></td>
<td><strong>插件挂载</strong></td>
<td>允许你在宿主机直接增删插件文件，无需进入容器内部。</td>
</tr>
<tr>
<td><code>-v gsuid_core_venv:/venv</code></td>
<td><strong>环境持久化</strong></td>
<td>使用命名卷存储 Python 虚拟环境，加快二次启动速度。</td>
</tr>
<tr>
<td><code>--add-host host.docker.internal:host-gateway</code></td>
<td><strong>映射宿主机 IP</strong></td>
<td>允许容器内部通过 <code>host.docker.internal</code> 访问宿主机上的服务。</td>
</tr>
<tr>
<td><code>-e http_proxy=...</code></td>
<td><strong>网络代理</strong></td>
<td>如果服务器在国内，设置此项可加速插件下载（需配合代理软件）。</td>
</tr>
<tr>
<td><code>-e UV_INDEX=...</code></td>
<td><strong>Python 镜像源</strong></td>
<td>可设置为阿里云或清华源，加速依赖包安装。</td>
</tr>
</tbody>
</table>
<p>@tab docker compose部署</p>
<p><strong>克隆源码并进入目录</strong></p>
<pre><code>git clone https://cnb.cool/gscore-mirror/gsuid_core.git
cd gsuid_core
</code></pre>
<p><strong>启动构建</strong></p>
<pre><code># 复制环境配置（可选）
cp .env.example .env
# 后台构建并启动
docker compose up -d --build
</code></pre>
<p>@tab 本地环境手动安装</p>
<p><strong>检查uv安装情况</strong></p>
<pre><code>uv -V
</code></pre>
<p>如果没有显示版本号，则需要下载</p>
<pre><code># 使用官方脚本一键安装
curl -LsSf https://astral.sh/uv/install.sh | sh

# 立即生效环境变量 (针对当前会话)
source $HOME/.cargo/env

# 验证安装是否成功
uv -V
</code></pre>
<p><strong>克隆源码并进入目录</strong></p>
<pre><code>git clone https://cnb.cool/gscore-mirror/gsuid_core.git
cd gsuid_core
</code></pre>
<p><strong>安装依赖</strong></p>
<pre><code># uv安装依赖方式
uv python install 3.13
uv sync --python 3.13
uv run python -m ensurepip
</code></pre>
<p><strong>启动</strong></p>
<pre><code># 当前目录下
uv run core
</code></pre>
<p>:::</p>
<h3>安装XutheringWavesUID与配套的签到插件RoverSign</h3>
<p>进入GsCore项目的根目录，进入<code>gsuid_core</code>文件夹，进入<code>plugins</code>文件夹</p>
<pre><code>git clone https://github.com/Loping151/XutheringWavesUID.git

git clone https://github.com/Loping151/RoverSign.git
</code></pre>
<p><strong>重启</strong>GsCore</p>
<h3>配置网页控制台</h3>
<h4>开放端口号</h4>
<p>GsCore默认运行在<code>8765</code>端口号，故而需要服务器配置中开放8765端口号</p>
<p>访问地址为：http://your-ip:8765/app</p>
<h4>获取注册码</h4>
<p>进入到早柚核心根目录，进入<code>data</code>文件夹，打开<code>config.json</code>文件，文件顶部在<code>masters</code>字段下填写你的QQ号，这是作为主人QQ</p>
<p>滑动到文件底部，可以看到<code>REGISTER_CODE</code>，复制它的值，这就是注册码，是注册网页控制台账户的<code>唯一凭证</code></p>
<h4>注册并进入网页控制台</h4>
<p>这里可以解决<code>99%</code>的配置问题</p>
<p>在网页控制台<code>Core核心配置</code>中填写<strong>WebSocket Token</strong>，这是为安全性考虑</p>
<hr />
<h2>上游机器人框架部署</h2>
<h3>部署NapCat与AstrBot</h3>
<p>:::tabs</p>
<p>@tab 同时部署NapCat与AstrBot</p>
<pre><code>mkdir astrbot
cd astrbot
wget https://raw.githubusercontent.com/NapNeko/NapCat-Docker/main/compose/astrbot.yml
sudo docker compose -f astrbot.yml up -d
</code></pre>
<p>@tab 单独部署AstrBot与NapCat</p>
<p><a href="/posts/astrbot/">点击查看如何使用Linux部署AstrBot</a></p>
<p><a href="/posts/napcatguide/">点击查看如何使用Linux部署NapCat</a></p>
<p>:::</p>
<h3>配置AstrBot连接GsCore</h3>
<ol>
<li>准备好在GsCore控制台填写的<strong>WebSocket Token</strong></li>
<li>进入AstrBot控制台，点击<code>插件市场</code>，搜索关键词<code>gscore</code>，点击安装，等待安装完成
如果没有安装成功则使用<code>镜像地址</code></li>
<li>进入<code>AstrBot插件</code>菜单，搜索关键词<code>gscore</code>，点击插件配置，填入<code>连接Core的WsToken</code></li>
</ol>
<h3>验证连接情况与主人设置</h3>
<p>在QQ中对机器人发送<code>corestatus</code>，如果成功返回图片，则代表当前用户为<code>主人用户</code>，且QQ已经正确连接到AstrBot
发送<code>ww帮助</code>,如果有帮助图片返回则证明插件运行成功，到此只剩下<strong>最后一步</strong></p>
<hr />
<h2>向作者申请伤害计算token以及接入全排行token</h2>
<p>总排行申请、反馈（仅限主人）：群号 387393347（需中转一次，因为被炸过群了）。如完全不使用QQ平台，可发邮件给客服小维 agent@loping151.com（记得写标题）申请计算服务token，要求附带具体使用平台、core信息截图和使用情况和规模说明，支持较少的请求量和用户量，但完全足以小范围使用，一般都欢迎入群。</p>
<ul>
<li>
<p>为什么需要申请总排行进行使用：</p>
<p>为了确保不被上传大量伪造数据影响持有率、排行等，以及 https://ngabbs.com/read.php?tid=45654606</p>
</li>
<li>
<p>总排行申请有无条件？</p>
<p>待在群里，潜水不沙</p>
</li>
<li>
<p>除了总排行，插件本体功能是否有其他内容需要申请：</p>
<p>无。评分功能由于是独立的服务，与插件本体无关。<img src="https://github.com/Loping151/XutheringWavesUID/raw/main/assets/4.webp" alt="为爱发电以及防剽窃成果被批控制欲强" /></p>
</li>
</ul>
<p>控制台填写token完成后，发送<code>ww下载全部资源</code></p>
<hr />
<h2>参考以及致谢</h2>
<ul>
<li>
<p>&lt;img src="https://xn--ntsp09fr3m.com/favicon.ico" height="70px" alt=""&gt;<a href="https://github.com/tyql688">Echo</a></p>
</li>
<li>
<p>&lt;img src="https://docs.sayu-bot.com/favicon.ico" height="70px" alt=""&gt;<a href="https://docs.sayu-bot.com">早柚核心</a></p>
</li>
<li>
<p><img src="./images/astrbot_icon.webp" alt="AstrBot" /> <a href="https://astrbot.app">AstrBot</a></p>
</li>
<li>
<p>&lt;img src="https://napcat.napneko.icu/assets/newnewlogo.webp" height="70px" alt=""&gt;<a href="https://napcat.napneko.icu">NapCat</a></p>
</li>
</ul>
<hr />
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>AstrBot部署教程</title>
    <link href="https://www.pardofelis.top/posts/e731ba28/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/e731ba28/</id>
    <published>2026-01-17T11:59:57.000Z</published>
    <updated>2026-04-18T06:35:41.000Z</updated>
    <summary>AstrBot在Docket部署以及配置方法</summary>
    <content type="html"><![CDATA[<blockquote>
<p>Ubuntu 24.04
AstrBot v4.11.2</p>
</blockquote>
<hr />
<h2>官方教程</h2>
<p><a href="http://docs.astrbot.app/deploy/platform/aiocqhttp/napcat.html">http://docs.astrbot.app/deploy/platform/aiocqhttp/napcat.html</a></p>
<hr />
<h2>部署</h2>
<blockquote>
<p>如果您的网络环境在中国大陆境内，上述命令将无法正常拉取。您可能需要修改 compose.yml 文件，将其中的 image: soulter/astrbot:latest 替换为 image: m.daocloud.io/docker.io/soulter/astrbot:latest</p>
</blockquote>
<pre><code>git clone https://github.com/AstrBotDevs/AstrBot
cd AstrBot
sudo docker-compose up -d
</code></pre>
<blockquote>
<p>docker-compose没安装可以用自己操作系统的包管理器安装一下
部署就这么多了喵</p>
</blockquote>
<hr />
<h2>安装配置NapCat</h2>
<p><a href="/posts/napcatguide/">NapCat部署教程</a></p>
<blockquote>
<p>创建反向websocket客户端</p>
</blockquote>
<p><img src="./images/QQ20260117-121456.webp" alt="AstrBot 截图 1 w-50%" /></p>
<blockquote>
<p>URL的选择</p>
</blockquote>
<p>:::tabs</p>
<p>@tab 与NapCat同环境</p>
<p>同环境意思是双方没有任何隔离
url:  ws://localhost:6199/ws</p>
<p>@tab 同一机器但Bot在Docker</p>
<p>Bot记得开放对应的端口号，不然NapCat无法访问
url:  ws://localhost:6199/ws
不行的话用url:  ws://内网IP:6199/ws</p>
<p>@tab 不同服务器</p>
<p>url:  ws://你的Bot框架所在的服务器的IP地址:6199/ws</p>
<ol>
<li>Bot记得开放对应的端口号，不然NapCat无法访问</li>
<li>如果是局域网那就用局域网IP</li>
<li>如果是云服务器就用公网IP</li>
<li>都不是那你还不如两个放一个服务器</li>
</ol>
<p>:::</p>
<hr />
<h2>连接NapCat</h2>
<ol>
<li>防火墙开放6185端口号</li>
<li>访问http://公网IP:6185</li>
</ol>
<p>记得改密码
<img src="./images/QQ20260117-122002.webp" alt="AstrBot 截图 2 w-50%" />
<img src="./images/QQ20260117-122050.webp" alt="AstrBot 截图 3 w-50%" />
<img src="./images/QQ20260117-122139.webp" alt="AstrBot 截图 4 w-50%" /></p>
<blockquote>
<p>这个的token填你在NapCat配置的token，机器人配置名字记得改，创建配置后将不允许更改</p>
</blockquote>
<p><img src="./images/QQ20260117-122235.webp" alt="AstrBot 截图 5 w-50%" /></p>
<hr />
<h3>改账号密码</h3>
<p><img src="./images/QQ20260117-123023.webp" alt="AstrBot 截图 6 w-50%" /></p>
<hr />
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>NapCat 部署</title>
    <link href="https://www.pardofelis.top/posts/3ae28f14/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/3ae28f14/</id>
    <published>2026-01-16T21:09:28.000Z</published>
    <updated>2026-04-18T06:35:41.000Z</updated>
    <summary>NapCat在Linux部署方法</summary>
    <content type="html"><![CDATA[<blockquote>
<p>NapCat v4.9.70</p>
</blockquote>
<hr />
<h2>风险</h2>
<p>:::warning
有被嘿壳冒用NapCat的API发反动消息的风险
token必须要设置，若因使用简单密码或者默认密码导致被人破解而导致机器人被黑，后果自负
:::</p>
<ol>
<li>首先需要设密码，反正什么都设密码，嘿壳没有密码难以冒用，中招的都是没有设密码的温室花朵</li>
<li>使用WebUI配置完成之后需要禁用6099端口号的防火墙</li>
<li>养成良好习惯，只开要用的端口号</li>
</ol>
<p><img src="./images/warning.webp" alt="看看喵 w-50%" /></p>
<hr />
<h2>部署NapCat</h2>
<p>官方网站-&gt; <a href="https://napcat.napneko.icu/">NapCatQQ | 现代化的基于 NTQQ 的 Bot 协议端实现</a></p>
<h3>非Docker部署方式</h3>
<blockquote>
<p>在Shell执行以下命令，失败就再试一次</p>
</blockquote>
<pre><code>curl -o \
napcat.sh \
https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh \
&amp;&amp; sudo bash napcat.sh \
--docker n \
--cli y
</code></pre>
<p><img src="./images/QQ_1768574096439.webp" alt="NapCat安装完成 w-50%" /></p>
<hr />
<h3>配置NapCat</h3>
<blockquote>
<p>输入&lt;u&gt;napcat&lt;/u&gt;命令</p>
</blockquote>
<p><img src="./images/QQ20260116-223347.webp" alt="NapCatGuide 截图 1 w-50%" />
<img src="./images/b6037eb46b524d6a586a2beb001d25f2.webp" alt="NapCatGuide 截图 2 w-50%" />
<img src="./images/QQ20260116-223612.webp" alt="NapCatGuide 截图 3 w-50%" />
<img src="./images/QQ20260117-095448.webp" alt="NapCatGuide 截图 4 w-50%" /></p>
<blockquote>
<p>需要填写作为机器人的QQ号，建议&lt;em&gt;使用小号&lt;/em&gt;</p>
</blockquote>
<p><img src="./images/QQ20260117-095524.webp" alt="NapCatGuide 截图 5 w-50%" /></p>
<p><img src="./images/QQ20260117-095600.webp" alt="NapCatGuide 截图 6 w-50%" /></p>
<blockquote>
<p>开机自启动，字面意思喵</p>
</blockquote>
<p><img src="./images/QQ20260117-100353.webp" alt="NapCatGuide 截图 7 w-50%" /></p>
<blockquote>
<p>配置WebUI</p>
</blockquote>
<p>:::warning
需要服务器放行6099端口号
:::</p>
<p><img src="./images/QQ20260117-100728.webp" alt="NapCatGuide 截图 8 w-50%" />
<img src="./images/QQ20260117-100821.webp" alt="NapCatGuide 截图 9 w-50%" /></p>
<hr />
<h3>启动NapCat</h3>
<p><img src="./images/QQ20260117-100500.webp" alt="NapCatGuide 截图 10 w-50%" />
<img src="./images/QQ20260117-100521.webp" alt="NapCatGuide 截图 11 w-50%" />
<img src="./images/QQ20260117-100534.webp" alt="NapCatGuide 截图 12 w-50%" />
<img src="./images/QQ20260117-100909.webp" alt="NapCatGuide 截图 13 w-50%" /></p>
<p>:::tip
解码网站-&gt; <a href="https://www.liantu.com/index_old.htm">https://www.liantu.com/index_old.htm</a>
:::
要是日志的二维码不能扫，就复制你的链接到解码网站生成二维码再扫
<img src="./images/QQ20260117-104858.webp" alt="NapCatGuide 截图 14 w-50%" /></p>
<hr />
<h3>退出脚本</h3>
<blockquote>
<p>手机上出现Linux登录即为成功
这个号不能在电脑登录，否则会将LinuxQQ挤下线</p>
</blockquote>
<p><img src="./images/Screenshot_2026-01-17-10-54-11-58_9d26c6446fd7bb8.webp" alt="NapCatGuide 截图 15 w-50%" /></p>
<blockquote>
<p>一直点&lt;kbd&gt;退出&lt;/kbd&gt;直到完全退出脚本</p>
</blockquote>
<p><img src="./images/QQ20260117-101017.webp" alt="NapCatGuide 截图 16 w-50%" /></p>
<hr />
<h2>登录WebUI，为下一步的Bot框架配置做准备</h2>
<blockquote>
<p>在浏览器输入URL
http://your_ipaddress:6099</p>
</blockquote>
<p>:::tip
密码是你在NapCat脚本配置WebUI的时候填的Token
:::</p>
<p><img src="./images/QQ20260117-112326.webp" alt="NapCatGuide 截图 17 w-50%" />
<img src="./images/QQ20260117-112547.webp" alt="NapCatGuide 截图 18 w-50%" />
<img src="./images/QQ20260117-112616.webp" alt="NapCatGuide 截图 19 w-50%" /></p>
<p>:::warning
URL根据你要接入的Bot框架而定
:::</p>
<p><img src="./images/QQ20260117-112641.webp" alt="NapCatGuide 截图 20 w-50%" /></p>
<hr />
<h2>Bot框架部署教程</h2>
<h3>TRSS-Yunzai</h3>
<p>官网-&gt; <a href="https://trss.me">trss.me</a></p>
<p><a href="/posts/yunzai-linux/">Yunzai在Linux部署教程</a></p>
<h3>AstrBot</h3>
<p>官网-&gt; <a href="https://astrbot.app/">https://astrbot.app/</a></p>
<p><a href="/posts/astrbot/">AstrBot部署教程</a></p>
<hr />
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>OneBot函数发送图片与表情包</title>
    <link href="https://www.pardofelis.top/posts/02ca1255/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/02ca1255/</id>
    <published>2025-10-21T21:06:13.000Z</published>
    <updated>2026-04-18T06:35:41.000Z</updated>
    <summary>image函数修改可以发送表情包</summary>
    <content type="html"><![CDATA[<pre><code>async function IMAGE(file, options = {}) {
    const imgSeg = segment.image(file)
    imgSeg.sub_type = options.isface ? 1 : 0
    return imgSeg
}
</code></pre>
<h3>示例</h3>
<pre><code>// 发送表情包
await IMAGE(fileUrl,{isface : true})

// 发送图片
await IMAGE(fileUrl,{isface : false})
</code></pre>
<hr />
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>Napcat的API</title>
    <link href="https://www.pardofelis.top/posts/ebe8fdac/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/ebe8fdac/</id>
    <published>2025-09-01T00:06:59.000Z</published>
    <updated>2026-04-18T06:35:41.000Z</updated>
    <summary>如何使用NapCat的API的教程</summary>
    <content type="html"><![CDATA[<blockquote>
<p>NapCat v4.9.70</p>
</blockquote>
<hr />
<p>:::info
前提是严格按照以下教程中关于NapCat的配置，否则需要注意端口号是否是6099
:::</p>
<p><a href="/posts/yunzai-linux/">Linux云崽部署教程</a></p>
<hr />
<h2>脚本设置方法</h2>
<p>输入以进入脚本</p>
<pre><code>napcat
</code></pre>
<p>来到脚本页面，进入<code>配置Napcat</code>，进入你的qq配置，配置HTTP服务器（正向http）
<img src="./images/httpsetting.webp" alt="HTTP服务配置" /></p>
<p>名称随意，但是不要和ws客户端重合，host改为127.0.0.1,port随意，默认也行，Token为了安全性必须写</p>
<h2>WebUI控制台设置方法</h2>
<h3>登录NapCat控制台</h3>
<p>:::info
打开网址
:::
:::tabs</p>
<p>@tab 本地运行Napcat</p>
<p><a href="http://localhost:6099">http://localhost:6099</a></p>
<p>@tab 云服务器运行</p>
<p>记得开放6099端口号
http://&lt;你的公网ip&gt;:6099</p>
<p>:::</p>
<p><img src="./images/napcat_befor_login.webp" alt="登录页面" />
你需要输入部署napcat时配置的密码</p>
<hr />
<h3>新建HTTP服务器</h3>
<p>按照如图步骤点击</p>
<p><img src="./images/napcat_logined.webp" alt="新建" /></p>
<hr />
<h3>启动HTTP服务器</h3>
<p>名称随便输入，打开启动，host改为<code>localhost</code>，输入token，点击保存</p>
<p>:::caution
如果不改host的情况下不加token，那我可以使用这个服务器控制你的qq，不改的话耗子尾汁
:::</p>
<p><img src="./images/httpServer.webp" alt="配置" /></p>
<hr />
<p>这个时候你会看到页面有两个框，一个是<code>HTTP服务器</code>，这个是刚刚创建的，另一个是<code>WebSocket客户端</code>，这是部署机器人时创建的，确保两个都在开启状态</p>
<p>:::info
这里由于我只是本地调用，所以host设置为localhost
:::</p>
<p><img src="./images/napcat_ok.webp" alt="检查是否成功" /></p>
<hr />
<h2>调用NapCat的API</h2>
<p>:::info
传入的url，若是本地的napcat则是http://127.0.0.1:3000，若是其他服务器上的，则127.0.0.1改成公网ip
:::</p>
<p><a href="https://napcat.apifox.cn/">NapCat-API地址</a></p>
<blockquote>
<p>可以看到全是post请求</p>
</blockquote>
<p>可以看到在跟api文档的对比中，多了<code>Authorization</code>字段，这是认证必须的</p>
<pre><code>import axios from 'axios'
const token = 你填写的HTTP服务器Token
 /**
     * 给指定 QQ 用户点赞
     * 
     * @async
     * @param {string} url - NapCat API 地址，例如 "http://127.0.0.1:3000"
     * @param {string|number} uid - 要点赞的 QQ 号
     * @param {number} times - 点赞次数
     * @returns {Promise&lt;Object&gt;} 返回 NapCat 接口响应数据
     * @throws {Error} 如果请求失败会抛出异常
     * 
     * @example
     * await NapCatAPI.thumbsUp("http://127.0.0.1:3000", 2331329306, 5);
     */
    async thumbsUp(url, uid, times) {
        try {
            const data = {
                user_id: uid,
                times: times
            };

            const response = await axios.post(`${url}/send_like`, data, {
                headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }
            });

            return response.data;
        } catch (error) {
            logger.error(`[Syuan-Plugin] 为 ${uid} 点赞失败: ${error}`);
            throw error;
        }
    },

</code></pre>
<hr />
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>Yunzai在Linux部署教程</title>
    <link href="https://www.pardofelis.top/posts/c9e7a2fa/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/c9e7a2fa/</id>
    <published>2025-08-29T13:33:26.000Z</published>
    <updated>2026-04-18T06:35:41.000Z</updated>
    <summary>基于Linux系统的Yunzai机器人部署教程</summary>
    <content type="html"><![CDATA[<blockquote>
<p>TRSS-Yunzai v3.1.3
NapCat v4.9.70</p>
</blockquote>
<hr />
<h2>前言</h2>
<p>:::warning
有相当一部分插件仓库都在Github，故而确保部署环境能够以HTTP或者SSH方式访问Github
:::</p>
<p>:::tabs</p>
<p>@tab HTTP方式</p>
<ol>
<li>测试连接(若无丢包则只需进行步骤1)</li>
</ol>
<pre><code>ping github.com
</code></pre>
<ol>
<li>若有丢包则命令行输入：</li>
</ol>
<pre><code>vim /etc/hosts
</code></pre>
<ol>
<li>在打开的文件中，<code>点击键盘上的</code> &lt;kbd&gt;i&lt;/kbd&gt;，左下角会出现<code>-- INSERT --</code>，将以下文本粘贴进文件中。</li>
</ol>
<pre><code>20.27.177.113   github.com
</code></pre>
<p>随后，按&lt;kbd&gt;ESC&lt;/kbd&gt;，键盘输入 &lt;kbd&gt;:&lt;/kbd&gt;+&lt;kbd&gt;w&lt;/kbd&gt;+&lt;kbd&gt;q&lt;/kbd&gt;+&lt;kbd&gt;Enter&lt;/kbd&gt;</p>
<blockquote>
<p>配置完成后可以到步骤1测试连接</p>
</blockquote>
<p>@tab SSH方式</p>
<ol>
<li>测试连接(若成功显示Github用户名则只需进行步骤1)</li>
</ol>
<pre><code>ssh -T git@github.com
</code></pre>
<ol>
<li>教程配置
<a href="https://blog.csdn.net/i89211/article/details/145216143?ops_request_misc=elastic_search_misc&amp;request_id=64b5dd981de5487c89d0cf6de6d6b6d2&amp;biz_id=0&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-145216143-null-null.142%5Ev102%5Epc_search_result_base3&amp;utm_term=linux%E7%9A%84ssh%E8%BF%9E%E6%8E%A5github&amp;spm=1018.2226.3001.4187">CSDN - Linux环境下使用SSH配置Github连接</a></li>
</ol>
<blockquote>
<p>配置完成后可以到步骤1测试连接</p>
</blockquote>
<p>:::</p>
<hr />
<h2>Bot部署</h2>
<p>Yunzai机器人的环境配置基于该脚本进行，详情可以访问官网 - <a href="https://trss.me/Install/Docker.html">Docker安装</a></p>
<p>:::warning
不要使用官网教程的自定义部分
:::</p>
<ol>
<li>在命令行输入以下命令</li>
</ol>
<pre><code>bash &lt;(curl -L gitee.com/TimeRainStarSky/TRSS_AllBot/raw/main/Install-Docker.sh)
</code></pre>
<ol>
<li>进入容器内部</li>
</ol>
<pre><code>tsab
</code></pre>
<ol>
<li>选择TRSS-Yunzai
点击安装，等待脚本运行完成即可。
<img src="./images/download.webp" alt="安装TRSS" /></li>
</ol>
<hr />
<h2>NapCat部署以及配置</h2>
<p><a href="/posts/napcatguide/">NapCat部署教程</a></p>
<blockquote>
<p>在WebUI中的反向Websocket客户端中填写</p>
</blockquote>
<p><img src="./images/QQ20260117-113750.webp" alt="Yunzai-Linux 截图 1 w-50%" /></p>
<blockquote>
<p>URL的选择</p>
</blockquote>
<p>:::tabs</p>
<p>@tab 与NapCat同环境</p>
<p>同环境意思是双方没有任何隔离
url:  ws://localhost:2536/OneBotv11</p>
<p>@tab 同一机器但Bot在Docker</p>
<p>Bot记得开放对应的端口号，不然NapCat无法访问
url:  ws://localhost:2536/OneBotv11
不行的话用url:  ws://内网IP:2536/OneBotv11</p>
<p>@tab 不同服务器</p>
<p>url:  ws://你的Bot框架所在的服务器的IP地址:2536/OneBotv11</p>
<ol>
<li>Bot记得开放对应的端口号，不然NapCat无法访问</li>
<li>如果是局域网那就用局域网IP</li>
<li>如果是云服务器就用公网IP</li>
<li>都不是那你还不如两个放一个服务器</li>
</ol>
<p>:::</p>
<h2>安装Guoba控制台</h2>
<ol>
<li>输入tsab命令进入脚本</li>
<li>按&lt;kbd&gt;5&lt;/kbd&gt;选中<code>TRSS-Yunzai</code>，点击<code>确认</code>进入</li>
<li>按&lt;kbd&gt;6&lt;/kbd&gt;选中<code>插件管理</code>，点击<code>确认</code>进入</li>
<li>按&lt;kbd&gt;2&lt;/kbd&gt;选中<code>Git插件管理</code>，点击<code>确认</code>进入</li>
<li>按&lt;kbd&gt;3&lt;/kbd&gt;选中<code>安装插件</code>，点击<code>确认</code>进入</li>
<li>按&lt;kbd&gt;2&lt;/kbd&gt;+&lt;kbd&gt;2&lt;/kbd&gt;选中<code>锅巴插件</code>，点击<code>确认</code>安装</li>
</ol>
<p>:::info
在这个界面也可以安装其他插件
:::</p>
<h2>启动Yunzai机器人</h2>
<ol>
<li>一直按&lt;kbd&gt;&lt;取消&gt;&lt;/kbd&gt;，直到第一行出现<code>打开 TRSS-Yunzai</code></li>
<li>点击3次&lt;kbd&gt;&lt;确认&gt;&lt;/kbd&gt;</li>
<li>观察到日志<code>OneBotv11(QQ) NapCat.Onebot v4.8.95 已连接</code>表示连接成功（不一定是v4.8.95）</li>
</ol>
<h2>配置主人</h2>
<ol>
<li>打开网址
:::tabs</li>
</ol>
<p>@tab 本地运行Yunzai</p>
<p><a href="http://localhost:2536/guoba">http://localhost:2536/guoba</a></p>
<p>@tab 云服务器运行</p>
<p>记得开放2536端口号
http://&lt;你的公网ip&gt;:2536/guoba</p>
<p>:::</p>
<ol>
<li>
<p>点击获取验证码</p>
</li>
<li>
<p>在<code>启动Yunzai机器人</code>步骤中打开的页面下会有显示</p>
</li>
</ol>
<pre><code>[16:20:35.723][MARK][  TRSSYz  ] [Guoba] 您正在请求验证码登录，若没有输出验证码，请将日志级别调整为 info 或以上
[16:20:35.723][INFO][  TRSSYz  ] ######################################################
[16:20:35.724][INFO][  TRSSYz  ] # [Guoba] 验证码登录请求                             #
[16:20:35.724][INFO][  TRSSYz  ] # 您的登录验证码为：eux1zsagp94ky810                 #
[16:20:35.724][INFO][  TRSSYz  ] # 验证码五分钟内有效且失效前不会再次打印，请尽快输入 #
[16:20:35.724][INFO][  TRSSYz  ] # 若非本人操作请忽略并考虑是否泄露了登录地址         #
[16:20:35.724][INFO][  TRSSYz  ] ######################################################
</code></pre>
<ol>
<li>
<p>输入验证码然后登录</p>
</li>
<li>
<p>进入配置管理，进入其他</p>
</li>
<li>
<p>新增主人账号，格式为<code>你的机器人的QQ账号:你需要成为主人的QQ号</code></p>
</li>
<li>
<p>页面往下滑到底，点击蓝色按钮&lt;kbd&gt;保存&lt;/kbd&gt;</p>
</li>
</ol>
<h2>配置服务器密钥</h2>
<blockquote>
<p>以适配napcat反向Websocket客户端的token</p>
</blockquote>
<p><img src="./images/server.webp" alt="服务器配置" /></p>
<ol>
<li>打开锅巴控制台下的基础配置</li>
<li>下滑到服务器配置</li>
<li>点击服务器鉴权并新增</li>
<li>鉴权标识填<code>Authorization</code></li>
<li>鉴权值填<code>Bearer</code>，然后加个空格，然后加上你在napcat反向Websocket客户端的token</li>
<li>点击下面的报错</li>
</ol>
<hr />
<p>至此，Linux操作系统下，TRSS-Yunzai机器人部署完成，剩下的插件需要你去挖掘，或者自己写。
附上部分插件列表
<a href="https://gitee.com/yhArcadia/Yunzai-Bot-plugins-index#/yhArcadia/Yunzai-Bot-plugins-index/blob/main/JS-Plugin.md">Yunzai插件索引</a></p>
<p><a href="/posts/napcatapi/">napcat接口调用教程</a></p>
<ul>
<li></li>
</ul>
<ul>
<li></li>
</ul>
<p>/</p>
<p>:::info
Yunzai自从不使用icqq后就失去了戳人等移动端功能，而Napcat的函数又刚好弥补了这一点
:::</p>
<hr />
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Bot"/>
  </entry>
  <entry>
    <title>Linux命令</title>
    <link href="https://www.pardofelis.top/posts/b690add3/" rel="alternate" type="text/html"/>
    <id>https://www.pardofelis.top/posts/b690add3/</id>
    <published>2025-05-16T16:56:26.000Z</published>
    <updated>2026-04-18T06:35:41.000Z</updated>
    <summary>Linux部分命令学习</summary>
    <content type="html"><![CDATA[<h2>Linux命令通用格式</h2>
<p>command [-option] [parameter]
<code>command</code>: 命令本体，如目录下文件夹/文件列举，命令<code>ls</code>
<code>option</code>：[可选，非必填] 命令的一些选项，用于控制命令的执行细节
<code>parameter</code>: [可选，非必填] 命令的参数，多用于命令的指向目标</p>
<h2>常用Linux命令</h2>
<h3>文件与目录操作</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
<th>常用参数示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ls</code></td>
<td>列出目录内容</td>
<td><code>ls -lha</code>（详细+隐藏）</td>
</tr>
<tr>
<td><code>cd</code></td>
<td>切换目录</td>
<td><code>cd ~</code>（回家目录）</td>
</tr>
<tr>
<td><code>pwd</code></td>
<td>显示当前目录路径</td>
<td></td>
</tr>
<tr>
<td><code>mkdir</code></td>
<td>创建目录</td>
<td><code>mkdir -p dir1/dir2</code>（递归创建）</td>
</tr>
<tr>
<td><code>rmdir</code></td>
<td>删除空目录</td>
<td></td>
</tr>
<tr>
<td><code>touch</code></td>
<td>创建空文件或更新时间戳</td>
<td></td>
</tr>
<tr>
<td><code>cp</code></td>
<td>复制文件/目录</td>
<td><code>cp -r dir1 dir2</code>（递归复制）</td>
</tr>
<tr>
<td><code>mv</code></td>
<td>移动/重命名文件</td>
<td><code>mv old.txt new.txt</code></td>
</tr>
<tr>
<td><code>rm</code></td>
<td>删除文件/目录</td>
<td><code>rm -rf dir</code>（强制递归删除）</td>
</tr>
<tr>
<td><code>cat</code></td>
<td>查看文件内容</td>
<td><code>cat file1 file2</code></td>
</tr>
<tr>
<td><code>more/less</code></td>
<td>分页查看文件</td>
<td><code>less -N file</code>（显示行号）</td>
</tr>
<tr>
<td><code>head/tail</code></td>
<td>查看文件首/尾几行</td>
<td><code>tail -f log.log</code>（实时追踪日志）</td>
</tr>
<tr>
<td><code>find</code></td>
<td>查找文件</td>
<td><code>find /home -name "*.txt"</code></td>
</tr>
<tr>
<td><code>grep</code></td>
<td>文本搜索</td>
<td><code>grep "error" log.txt</code></td>
</tr>
<tr>
<td><code>tar</code></td>
<td>压缩/解压文件</td>
<td><code>tar -czvf archive.tar.gz dir</code></td>
</tr>
</tbody>
</table>
<h3>权限与用户管理</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>chmod</code></td>
<td>修改文件权限</td>
<td><code>chmod 755 script.sh</code></td>
</tr>
<tr>
<td><code>chown</code></td>
<td>修改文件所有者</td>
<td><code>chown user:group file</code></td>
</tr>
<tr>
<td><code>sudo</code></td>
<td>以超级用户权限执行命令</td>
<td><code>sudo apt update</code></td>
</tr>
<tr>
<td><code>passwd</code></td>
<td>修改用户密码</td>
<td><code>passwd username</code></td>
</tr>
<tr>
<td><code>useradd/userdel</code></td>
<td>添加/删除用户</td>
<td><code>useradd -m newuser</code></td>
</tr>
<tr>
<td><code>su</code></td>
<td>切换用户</td>
<td><code>su - username</code></td>
</tr>
</tbody>
</table>
<h3>系统管理与进程</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ps</code></td>
<td>查看进程</td>
<td><code>ps aux | grep nginx</code></td>
</tr>
<tr>
<td><code>top/htop</code></td>
<td>动态查看系统资源占用</td>
<td><code>top</code>（按q退出）</td>
</tr>
<tr>
<td><code>kill</code></td>
<td>终止进程</td>
<td><code>kill -9 PID</code>（强制终止）</td>
</tr>
<tr>
<td><code>df</code></td>
<td>查看磁盘空间</td>
<td><code>df -h</code>（易读格式）</td>
</tr>
<tr>
<td><code>du</code></td>
<td>查看目录占用空间</td>
<td><code>du -sh /home</code></td>
</tr>
<tr>
<td><code>free</code></td>
<td>查看内存使用情况</td>
<td><code>free -m</code>（MB为单位）</td>
</tr>
<tr>
<td><code>uname</code></td>
<td>查看系统信息</td>
<td><code>uname -a</code>（全部信息）</td>
</tr>
<tr>
<td><code>shutdown/reboot</code></td>
<td>关机/重启</td>
<td><code>shutdown -h now</code></td>
</tr>
</tbody>
</table>
<h3>网络相关</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ping</code></td>
<td>测试网络连通性</td>
<td><code>ping google.com</code></td>
</tr>
<tr>
<td><code>ifconfig/ip</code></td>
<td>查看/配置网络接口</td>
<td><code>ip addr show</code></td>
</tr>
<tr>
<td><code>netstat</code></td>
<td>查看网络连接和端口</td>
<td><code>netstat -tulnp</code></td>
</tr>
<tr>
<td><code>ssh</code></td>
<td>远程登录</td>
<td><code>ssh user@192.168.1.1</code></td>
</tr>
<tr>
<td><code>scp</code></td>
<td>安全复制文件（基于SSH）</td>
<td><code>scp file.txt user@host:/path</code></td>
</tr>
<tr>
<td><code>wget/curl</code></td>
<td>下载文件或访问URL</td>
<td><code>curl -O http://example.com/file</code></td>
</tr>
</tbody>
</table>
<h3>软件包管理</h3>
<table>
<thead>
<tr>
<th>命令（不同发行版）</th>
<th>作用</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>apt</code> (Debian/Ubuntu)</td>
<td>管理软件包</td>
<td><code>sudo apt install nginx</code></td>
</tr>
<tr>
<td><code>yum/dnf</code> (RHEL/CentOS)</td>
<td>管理软件包</td>
<td><code>sudo yum install httpd</code></td>
</tr>
<tr>
<td><code>pacman</code> (Arch)</td>
<td>管理软件包</td>
<td><code>sudo pacman -S firefox</code></td>
</tr>
</tbody>
</table>
<h3>其他实用命令</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>echo</code></td>
<td>输出文本（常用于脚本）</td>
</tr>
<tr>
<td><code>date</code></td>
<td>显示或设置系统时间</td>
</tr>
<tr>
<td><code>alias</code></td>
<td>创建命令别名（如 <code>alias ll='ls -l'</code>）</td>
</tr>
<tr>
<td><code>history</code></td>
<td>查看命令历史记录</td>
</tr>
<tr>
<td><code>man</code></td>
<td>查看命令手册（如 <code>man ls</code>）</td>
</tr>
</tbody>
</table>
<h3>tmux</h3>
<blockquote>
<p>创建一个名为 session-name 的新会话</p>
</blockquote>
<pre><code>tmux new -s session-name
</code></pre>
<blockquote>
<p>进入已经存在的会话</p>
</blockquote>
<pre><code>tmux attach -t session-name
</code></pre>
<blockquote>
<p>查看会话列表</p>
</blockquote>
<pre><code>tmux ls
</code></pre>
<blockquote>
<p>终止会话</p>
</blockquote>
<pre><code>tmux kill-session -t session-name
</code></pre>
<h4>tmux窗口命令(来源trss.me)</h4>
<table>
<thead>
<tr>
<th>名称</th>
<th>用法</th>
</tr>
</thead>
<tbody>
<tr>
<td>离开窗口</td>
<td>Ctrl+B D</td>
</tr>
<tr>
<td>切换窗口</td>
<td>Ctrl+B W</td>
</tr>
<tr>
<td>新建窗口</td>
<td>Ctrl+B C</td>
</tr>
<tr>
<td>分屏</td>
<td>Ctrl+B -/_</td>
</tr>
<tr>
<td>切换鼠标模式</td>
<td>按住 Shift</td>
</tr>
<tr>
<td>切换复制模式</td>
<td>Ctrl+B ↲</td>
</tr>
<tr>
<td>杀死窗口</td>
<td>Ctrl+B X Y</td>
</tr>
</tbody>
</table>
<h2>常见命令</h2>
<h3>Linux中一般怎么隐藏文件</h3>
<blockquote>
<p>文件名以<code>.</code>开头</p>
</blockquote>
<hr />
<h3>如何查看CPU信息</h3>
<blockquote>
<p>lscpu</p>
</blockquote>
<hr />
<h3>查看占用 CPU 使用率最高的进程</h3>
<blockquote>
<p>top</p>
</blockquote>
<hr />
<h3>如何过滤文件中包括“ERROR”的行</h3>
<blockquote>
<p>grep "ERROR" 文件名</p>
</blockquote>
<hr />
<h3>怎么查看某端口号</h3>
<blockquote>
<p>sudo netstat -tuln | grep :端口号</p>
</blockquote>
<hr />
<h3>查看某进程号</h3>
<blockquote>
<p>ps aux | grep 进程名
ps -ef  | grep ps_name</p>
</blockquote>
<hr />
<h3>grep与find区别？grep用法</h3>
<table>
<thead>
<tr>
<th>比较项</th>
<th><code>grep</code></th>
<th><code>find</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>作用</td>
<td><strong>搜索文件内容</strong> 中是否包含某个关键词</td>
<td><strong>查找文件名/路径</strong> 是否匹配某条件</td>
</tr>
<tr>
<td>搜索对象</td>
<td>文件内容中的文本行</td>
<td>文件本身（按名字、大小、时间等）</td>
</tr>
<tr>
<td>示例</td>
<td>在文件中查找包含 "error" 的行</td>
<td>查找当前目录下所有 <code>.log</code> 文件</td>
</tr>
<tr>
<td>常见使用场景</td>
<td>找出日志中含 "ERROR" 的行</td>
<td>查找最近 7 天修改过的文件</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>参数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-i</code></td>
<td>忽略大小写</td>
</tr>
<tr>
<td><code>-n</code></td>
<td>显示匹配行的行号</td>
</tr>
<tr>
<td><code>-v</code></td>
<td>显示不匹配的行（取反）</td>
</tr>
<tr>
<td><code>-r</code> / <code>-R</code></td>
<td>递归搜索子目录下所有文件</td>
</tr>
<tr>
<td><code>-E</code></td>
<td>使用扩展正则表达式（等同于 <code>egrep</code>）</td>
</tr>
<tr>
<td><code>--color=auto</code></td>
<td>匹配关键词高亮（现代系统通常默认开启）</td>
</tr>
</tbody>
</table>
<hr />
<h3>查看IP地址</h3>
<blockquote>
<p>内网IP ip addr show
公网IP curl ifconfig.me</p>
</blockquote>
<hr />
<h3>查看家目录下的t.txt文件</h3>
<blockquote>
<p>cat ~/t.txt</p>
</blockquote>
<hr />
<h3>怎么查出tomcat这个进程并杀死它</h3>
<blockquote>
<p>pgrep -af tomcat
kill -9 pid</p>
</blockquote>
<hr />
<h3>动态查看日志</h3>
<blockquote>
<p>tail -f &lt;日志文件路径&gt;</p>
</blockquote>
<hr />
<h3>查看系统磁盘空间</h3>
<blockquote>
<p>df -h</p>
</blockquote>
<hr />
<h3>查看当前机器监听的所有端口</h3>
<blockquote>
<p>ss -tuln</p>
</blockquote>
<hr />
<h3>把一个文件夹打包成.tar.gz,以及解压拆包命令</h3>
<blockquote>
<p>tar -czvf archive.tar.gz folder_name</p>
</blockquote>
<ul>
<li>
<p>c：创建压缩包</p>
</li>
<li>
<p>z：使用 gzip 压缩</p>
</li>
<li>
<p>v：显示过程</p>
</li>
<li>
<p>f：指定文件名</p>
</li>
</ul>
<blockquote>
<p>tar -xzvf archive.tar.gz</p>
</blockquote>
<hr />
<h3>以/etc/password的前五行内容为例，提取用户名</h3>
<blockquote>
<p>head -n 5 /etc/passwd | cut -d: -f1</p>
</blockquote>
<p>head -n 5：取前五行
cut -d: -f1：按冒号分隔，取第一个字段（用户名）</p>
<hr />
<h3>cron后台常驻程序(daemon)用于？</h3>
<blockquote>
<p>cron 是 Linux 中的计划任务守护进程，用于定期执行预定的任务，如备份、自动更新、发送报告等。</p>
</blockquote>
<hr />
<h3>哪个命令可以列出定义在以后特定时间运行一次的所有任务？</h3>
<blockquote>
<p>atq</p>
</blockquote>
<hr />
<h3>bash中export命令的作用</h3>
<blockquote>
<p>将变量提升为环境变量，使其在当前 shell 启动的所有子进程中生效</p>
</blockquote>
<hr />
<h3>当前目录下还有多大空间</h3>
<blockquote>
<p>df -h .</p>
</blockquote>
<hr />
<h3>有个备份程序a,需要在周一到周五下午1点和晚上8点各运行一次,什么corntab可以完成这个工作</h3>
<blockquote>
<p>0 13,20 * * 1-5 /path/to/a</p>
</blockquote>
<table>
<thead>
<tr>
<th>字段位置</th>
<th>含义</th>
<th>设置值</th>
</tr>
</thead>
<tbody>
<tr>
<td>分钟</td>
<td>每小时第几分钟</td>
<td><code>0</code>（整点）</td>
</tr>
<tr>
<td>小时</td>
<td>每天第几个小时</td>
<td><code>13,20</code>（13点 和 20点）</td>
</tr>
<tr>
<td>日</td>
<td>每月第几天</td>
<td><code>*</code>（每天）</td>
</tr>
<tr>
<td>月</td>
<td>每年第几月</td>
<td><code>*</code>（每月）</td>
</tr>
<tr>
<td>周</td>
<td>每周星期几</td>
<td><code>1-5</code>（周一到周五）</td>
</tr>
</tbody>
</table>
<hr />
<h3>怎么从当前系统中卸载一个已经安装的文件系统</h3>
<blockquote>
<p>umount &lt;挂载点路径&gt;</p>
</blockquote>
<hr />
<h3>哪条命令用来装载所有在/etc/fstab目录下定义的文件系统</h3>
<blockquote>
<p>mount -a</p>
</blockquote>
<hr />
<h3>把f1.txt复制到f2.txt</h3>
<blockquote>
<p>cat f1.txt &gt; f2.txt</p>
</blockquote>
<hr />
<h3>快速切换到j的家目录下</h3>
<blockquote>
<p>cd ~j</p>
</blockquote>
<hr />
<h3>在文件中查找以*开头的行</h3>
<blockquote>
<p>grep '^*' filename</p>
</blockquote>
<hr />
<h3>ps中什么参数是显示所有用户的进程</h3>
<blockquote>
<p>ps -ef (e=a)</p>
</blockquote>
<hr />
<h3>一行后面加入怎么表示未结束，下一行继续</h3>
<blockquote>
<p>\</p>
</blockquote>
<hr />
<h3>命令 kill -9 的含义是</h3>
<blockquote>
<p>立即强制终止进程，无法被拦截或处理</p>
</blockquote>
<hr />
<h3>对所有用户的变量设置应该放在哪个文件下</h3>
<blockquote>
<p>/etc/profile</p>
</blockquote>
<hr />
<h3>linux脚本文件一般以什么开头</h3>
<pre><code>    #!/bin/bash
</code></pre>
<hr />
<h3>linux中提供TCP/IP包过滤功能的软件叫什么</h3>
<blockquote>
<p>iptables</p>
</blockquote>
<hr />
<h3>vi中退出不保存的命令</h3>
<blockquote>
<p>:q!</p>
</blockquote>
<hr />
<h3>哪个命令可以压缩部分文件</h3>
<blockquote>
<p>tar -czf archive.tar.gz file1 file2 dir/</p>
</blockquote>
<hr />
<h3>对于Nginx服务器，提供子进程的缺省的用户是</h3>
<blockquote>
<p>www-data
grep ^user /etc/nginx/nginx.conf 查看Nginx的默认用户</p>
</blockquote>
<hr />
<h3>Nginx的主配置文件</h3>
<blockquote>
<p>/etc/nginx/nginx.conf</p>
</blockquote>
<hr />
<h3>什么命令解压缩tar文件</h3>
<blockquote>
<p>tar -xvf archive.tar</p>
</blockquote>
<hr />
<h3>netstat -a 命令停了很久没反应，可能是哪里的原因</h3>
<blockquote>
<p>可能是由于 DNS 解析慢造成的
netstat -an
<code>n</code>跳过域名解析，直接显示IP和端口</p>
</blockquote>
<hr />
<h3>telnet命令</h3>
<blockquote>
<p>用于测试 TCP 端口连通性（是否能连接到某个主机+端口）
telnet ip &lt;端口号&gt;</p>
</blockquote>
<hr />
]]></content>
    <author><name>所愿Syuan</name></author>
    <category term="Study"/>
  </entry>
</feed>
