<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dukeの快乐老家</title><description>Blog Site</description><link>https://duke486.com/</link><language>zh_CN</language><item><title>从零搭建：CD2+Symedia+FastEmby实现302观影体验</title><link>https://duke486.com/posts/%E4%BB%8E%E9%9B%B6%E6%90%AD%E5%BB%BAcd2symediafastemby%E5%AE%9E%E7%8E%B0302%E8%A7%82%E5%BD%B1%E4%BD%93%E9%AA%8C/</link><guid isPermaLink="true">https://duke486.com/posts/%E4%BB%8E%E9%9B%B6%E6%90%AD%E5%BB%BAcd2symediafastemby%E5%AE%9E%E7%8E%B0302%E8%A7%82%E5%BD%B1%E4%BD%93%E9%AA%8C/</guid><description>利用 Symedia 和 FastEmby 搭建一套基于 115/123 网盘的 302 直连影视库，解决卡顿痛点。</description><pubDate>Wed, 04 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::warning&lt;br /&gt;
全文由人工撰写并通过AI寻找语病订正，如果无法接受可退出。&lt;br /&gt;
:::&lt;/p&gt;
&lt;h2&gt;1. 概览与先决条件&lt;/h2&gt;
&lt;p&gt;本文将在一台云服务器上，手把手带你从零开始搭建基于 115 网盘或 123 网盘的影视库。我们将结合 Symedia 进行自动化管理，并提供 Emby 服务端与 Infuse 直连两种观影路径。&lt;/p&gt;
&lt;p&gt;在正式开始之前，我们需要理清整体的架构逻辑。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204151723-pirhfoz.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前情提要：&lt;/strong&gt;
115 网盘对开放 API 接口有着极其严格的速率限制。这就导致了一个非常现实的问题：无论是使用刮削软件、Emby 还是 Infuse，如果选择直接挂载 115 网盘进行读取，扫库速度会慢到令人发指。添加新影片后，刮削入库的过程繁琐且漫长。更糟的是，Infuse 在直接挂载 115 网盘时，请求限制往往导致扫库卡顿，甚至无法起播。&lt;/p&gt;
&lt;p&gt;与此同时，Infuse 虽然好用，但也有局限性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;在线元数据模式&lt;/strong&gt;：无法获取 &lt;strong&gt;TMDB 里不存在的那类&lt;/strong&gt; 影片信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;离线元数据模式&lt;/strong&gt;：识别率感人。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，引入 Emby 作为后端来统一管理元数据，是提高观影体验的必要手段。&lt;/p&gt;
&lt;p&gt;:::note&lt;br /&gt;
&lt;strong&gt;主要方案流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;存储&lt;/strong&gt;：115网盘 / 123网盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;挂载&lt;/strong&gt;：通过 CloudDrive (CD2) 挂载为本地目录&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;管理&lt;/strong&gt;：Symedia 读取网盘目录树，生成指向本地路径的 STRM 文件，并自动下载影片元数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;媒体库&lt;/strong&gt;：Emby 直接扫描 STRM 文件极速入库&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;播放（302）&lt;/strong&gt; ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端连接 FastEmby 反代端口&lt;/li&gt;
&lt;li&gt;STRM 链接被拦截并改写&lt;/li&gt;
&lt;li&gt;115 视频由 FastEmby 获取直链&lt;/li&gt;
&lt;li&gt;123 等其他盘由 OpenList 302 挂载模式获取直链&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;客户端&lt;/strong&gt;：Infuse/Emby直连网盘地址流畅观看&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;然而，原生的 Emby 在起播 STRM 文件之前，有一个顽固的机制：它总是会尝试读取文件头部，使用 &lt;code&gt;ffprobe -i&lt;/code&gt; 获取视频的编码、音轨等详细信息。这带来了三个严重的副作用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;起播龟速&lt;/strong&gt;：客户端点击播放后需要转圈很久。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流量爆炸&lt;/strong&gt;：服务端为了读取文件头，会消耗大量下行流量，对于按流量计费的服务器不友好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能崩溃&lt;/strong&gt;：在处理结构不良的 MKV 或 ISO 原盘文件时，可能消耗上 GB 的流量并耗尽低配服务器的内存与磁盘 IO，直接导致假死。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在 Symedia 的官方 Wiki 中有关于此问题的详细描述：&lt;a href=&quot;https://wiki.viplee.cc/symedia/use/strm_assistant/&quot;&gt;Symedia Wiki-神医插件&lt;/a&gt;。简单来说：这款 Emby 插件可以有效解决问题 1（起播慢），但对问题 2 和 3 的改善有限。&lt;/p&gt;
&lt;p&gt;因此，针对服务端配置较低、或者对起播速度有极致要求的场景，这里也提供一套去除了 Emby 的&lt;strong&gt;备选方案&lt;/strong&gt;。代价是牺牲了媒体库的自由划分能力，且无法展示&lt;strong&gt;特殊&lt;/strong&gt;影片的海报墙。但经实测，该方案针对 Remux 动漫和电影，起播速度可压缩至 2-5 秒。&lt;/p&gt;
&lt;p&gt;:::note&lt;br /&gt;
&lt;strong&gt;备选方案流程（极速起播）：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;存储&lt;/strong&gt;：115网盘 / 123网盘&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;挂载&lt;/strong&gt;：CloudDrive (CD2) 挂载&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;管理&lt;/strong&gt;：Symedia 生成指向 OpenList 的 STRM 文件（而非本地路径），并下载元数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;播放（302）&lt;/strong&gt; ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Infuse 直接挂载包含 STRM 的文件夹（通过 WebDAV）&lt;/li&gt;
&lt;li&gt;所有视频请求由 OpenList 302 模式获取直链&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;终端&lt;/strong&gt;：客户端直连网盘观看&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;如果既想观看特殊影片，又希望普通影片在低配服务器上秒播，可以混合使用这两种方案。即：普通影片走备选方案（Infuse 直连），特殊影片走主要方案（Emby 管理）。&lt;/p&gt;
&lt;p&gt;让我们开始吧！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本文所需的前置条件：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;:::important&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一台 NAS 或云服务器（必需）&lt;/li&gt;
&lt;li&gt;115 网盘或 123 网盘账号（必需，需付费）&lt;/li&gt;
&lt;li&gt;Symedia 激活码（必需，179 元）&lt;/li&gt;
&lt;li&gt;Infuse PRO（备选方案必需，需付费）&lt;/li&gt;
&lt;li&gt;CloudDrive 会员（若挂载多个网盘则必需）&lt;/li&gt;
&lt;li&gt;Emby 激活码（可选，也可寻找某种版本）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;2. 服务端准备工作&lt;/h2&gt;
&lt;h3&gt;2.1 虚空终端（网络环境配置）&lt;/h3&gt;
&lt;p&gt;Symedia 在刮削元数据、通过 Bot 推送通知以及我们拉取 Docker 镜像时，需要一个相对科学的网络环境。&lt;/p&gt;
&lt;p&gt;如果你使用的是家用 NAS，解决方式较多：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;旁路网关&lt;/strong&gt;：在局域网内运行基于&lt;a href=&quot;https://wiki.metacubex.one&quot;&gt;虚空终端&lt;/a&gt;的软件，开启“允许局域网连接”，获取内网代理地址（如 &lt;code&gt;192.168.1.x:7890&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;透明代理&lt;/strong&gt;：在主路由或软路由层面解决&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本机运行&lt;/strong&gt;：直接在服务端运行虚空终端核心&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面重点介绍&lt;strong&gt;方法 3&lt;/strong&gt;，它同时适用于云服务器和 NAS 环境。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;下载核心&lt;/strong&gt;：用电脑下载适用于你服务器架构（通常是 &lt;code&gt;linux-amd64&lt;/code&gt;​）的&lt;a href=&quot;https://wiki.metacubex.one&quot;&gt;虚空终端&lt;/a&gt;核心并解压。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;上传文件&lt;/strong&gt;：将核心文件上传到服务器的工作目录，假设重命名为 &lt;code&gt;mihomo-linux-amd64&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;安装与配置&lt;/strong&gt;：执行以下命令，创建目录并赋予权限。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mv mihomo-linux-amd64 /usr/local/bin/mihomo
sudo chmod +x /usr/local/bin/mihomo
sudo mkdir -p /etc/mihomo
sudo vi /etc/mihomo/config.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编辑配置文件&lt;/strong&gt;：先填入下面的基础配置，然后从你自己的订阅配置文件中复制 &lt;code&gt;proxies&lt;/code&gt;​、&lt;code&gt;proxy-groups&lt;/code&gt;​ 和 &lt;code&gt;rules&lt;/code&gt; 部分填入下方。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 基础端口设置
port: 7890
socks-port: 7891
mixed-port: 7893
allow-lan: true
mode: Rule
log-level: info
ipv6: true
external-controller: &apos;0.0.0.0:9990&apos;
secret: &quot;您的后台密码&quot;
external-ui: /etc/mihomo/ui

# --- 关键：TUN 模式配置 ---
# 在拉取 Docker 镜像时非常有用，日常运行可关闭
tun:
  enable: false
  stack: system # 或者 gvisor
  dns-hijack:
    - any:53
  auto-route: true
  auto-detect-interface: true

# --- 关键：DNS 配置 (配合 TUN 必须开启 fake-ip 或 redir-host) ---
dns:
  enable: true
  listen: 0.0.0.0:53
  enhanced-mode: fake-ip
  nameserver:
    - 8.8.8.8
    - 1.1.1.1

# --- 请在下方粘贴您的 proxies, proxy-groups, rules ---
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置 UI 面板&lt;/strong&gt;：下载 &lt;a href=&quot;https://github.com/MetaCubeX/metacubexd&quot;&gt;Metacubexd&lt;/a&gt; 或其他 UI 的 release 压缩包，解压后将文件夹内的所有文件放入 &lt;code&gt;/etc/mihomo/ui&lt;/code&gt;​。&lt;br /&gt;
&lt;em&gt;注意：运行时如果提示缺少 MMDB 文件，请自行下载&lt;/em&gt; &lt;em&gt;​&lt;code&gt;Country.mmdb&lt;/code&gt;​&lt;/em&gt;​ &lt;em&gt;放入&lt;/em&gt;  &lt;em&gt;​&lt;code&gt;/etc/mihomo&lt;/code&gt;​&lt;/em&gt;​ &lt;em&gt;目录。&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;设置开机自启&lt;/strong&gt;：&lt;br /&gt;
创建服务文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nano /etc/systemd/system/mihomo.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;填入内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=Mihomo Daemon
After=network.target

[Service]
Type=simple
User=root
# TUN 模式必须用 root 权限运行
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
Restart=on-failure

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable mihomo --now
sudo systemctl status mihomo
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;验证&lt;/strong&gt;：访问 &lt;code&gt;http://服务器IP:9990/ui&lt;/code&gt;，填入服务器 IP 和密码即可进入控制面板。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.2 Docker 环境&lt;/h3&gt;
&lt;p&gt;在安装 Docker 和拉取镜像之前，建议先在虚空终端的网页面板中，开启 &lt;code&gt;“配置” -&amp;gt; “开启 TUN 转发”&lt;/code&gt;​。&lt;strong&gt;下载完成后记得关闭此开关，以免影响其他服务。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204151648-yyp1qus.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;安装 Docker ：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -fsSL https://get.docker.com | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 安装 CD2 与 OpenList&lt;/h2&gt;
&lt;h3&gt;CloudDrive2 (CD2)&lt;/h3&gt;
&lt;p&gt;此处不使用 Docker 版本的 CD2，前往 &lt;a href=&quot;https://www.clouddrive2.com/download.html&quot;&gt;CloudDrive2 官网&lt;/a&gt; 下载 &lt;code&gt;Linux x86_64&lt;/code&gt; 版本。&lt;/p&gt;
&lt;p&gt;请详细阅读 &lt;a href=&quot;https://www.clouddrive2.com/help.html&quot;&gt;CD2 官方教程&lt;/a&gt;。将下载的 &lt;code&gt;tgz&lt;/code&gt;​ 包解压到你的工作目录，例如 &lt;code&gt;/root&lt;/code&gt;​。解压后，最终的可执行文件路径应类似：&lt;code&gt;/root/clouddrive-2-linux-x86_64-x.x.x/clouddrive&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;设置开机自启服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nano /etc/systemd/system/clouddrive.service
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=CloudDrive Daemon
After=network.target

[Service]
Type=simple
User=root
# 请根据实际解压路径修改 WorkingDirectory 和 ExecStart
WorkingDirectory=/root/clouddrive-2-linux-x86_64-你的版本号
ExecStart=/root/clouddrive-2-linux-x86_64-你的版本号/clouddrive
Restart=on-failure

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动 CD2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl enable clouddrive --now
sudo systemctl status clouddrive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，可通过 &lt;code&gt;http://服务器IP:19798&lt;/code&gt; 访问 CD2 管理页面。&lt;/p&gt;
&lt;h3&gt;OpenList&lt;/h3&gt;
&lt;p&gt;OpenList 是一个轻量级的目录列表程序，支持 302 重定向，是本方案中处理非 115 网盘（如 123 盘）直链的工具。&lt;/p&gt;
&lt;p&gt;使用官方一键脚本安装（脚本可能随时间更新，建议到官网&lt;a href=&quot;https://doc.oplist.org/guide/installation/script&quot;&gt;一键安装&lt;/a&gt;寻找最新脚本）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -fsSL https://res.oplist.org/script/v4.sh &amp;gt; install-openlist-v4.sh &amp;amp;&amp;amp; sudo bash install-openlist-v4.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;脚本运行过程中选择“安装OpenList”即可。安装完成后，通过 &lt;code&gt;http://服务器IP:5244&lt;/code&gt; 访问。初始用户名和密码会显示在终端输出中，请务必记录。如果遗忘，可再次运行脚本重置密码。&lt;/p&gt;
&lt;h2&gt;4. 创建 Symedia、Emby、FastEmby 容器&lt;/h2&gt;
&lt;p&gt;这一步涉及到路径映射，逻辑需要非常清晰。&lt;/p&gt;
&lt;p&gt;我们将通过 CD2 把网盘挂载到宿主机的本地路径。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于群晖/飞牛等 NAS，通常挂载到 &lt;code&gt;/volume1/CloudNAS&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对于普通 Linux 服务器，推荐挂载到 &lt;code&gt;/mnt/CloudNAS&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关键：&lt;/strong&gt;  本文不使用 Docker 化的 CD2，因此挂载路径完全由我们控制。为了统一后续配置，我们假设宿主机的挂载点为 &lt;code&gt;/mnt/CloudNAS/CloudDrive&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;:::note&lt;br /&gt;
&lt;strong&gt;关于 Symedia 官方 Wiki 的说明：&lt;/strong&gt;
Wiki 中的逻辑是针对“全容器化”场景，即网盘在宿主机的路径和在 CD2 容器内的路径存在映射关系。由于我们直接在宿主机运行 CD2，请勿参考 Wiki 中“标准挂载”部分的映射逻辑。&lt;br /&gt;
:::&lt;/p&gt;
&lt;p&gt;请直接复制以下命令创建容器，确保路径一致。&lt;/p&gt;
&lt;h3&gt;Symedia&lt;/h3&gt;
&lt;p&gt;Symedia 负责刮削、整理和生成 STRM。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d \
    --name symedia \
    -e TZ=Asia/Shanghai \
    -e LICENSE_KEY=你的Symedia激活码 \
    -e COVER_MAKER=true \
    -e HTTP_PROXY=http://127.0.0.1:7890 \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    -v ./config:/app/config \
    -v ./playwright:/symedia/.cache/ms-playwright \
    -v ./fonts:/app/static/fonts \
    -v /mnt/CloudNAS:/CloudNAS:rslave \
    -v /mnt/media_strm:/media \
    -p 8095:8095 \
    --restart always \
    --network host \
    shenxianmq/symedia:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;HTTP_PROXY&lt;/code&gt;​：指向你搭建的虚空终端。如果 Symedia 使用 &lt;code&gt;host&lt;/code&gt;​ 网络模式，填 &lt;code&gt;127.0.0.1:7890&lt;/code&gt; 即可；否则需填服务器内网 IP。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;/mnt/CloudNAS&lt;/code&gt;：这是 CD2 挂载网盘的根目录的上级目录。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;/mnt/media_strm&lt;/code&gt;：这是 Symedia 生成的 STRM 文件存放的本地路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;访问端口：&lt;code&gt;8095&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Emby&lt;/h3&gt;
&lt;p&gt;媒体服务器核心。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d \
  --name emby_server \
  --restart always \
  --network host \
  -e PUID=0 \
  -e PGID=0 \
  -e TZ=Asia/Shanghai \
  -v /mnt/emby_data:/config \
  -v /mnt/CloudNAS:/CloudNAS:rslave \
  -v /mnt/media_strm:/media \
  linuxserver/emby:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;/mnt/emby_data&lt;/code&gt;：Emby 的配置数据目录，可以改变。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;/mnt/media_strm:/media&lt;/code&gt;：将 Symedia 生成的 STRM 映射给 Emby 读取。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;访问端口：&lt;code&gt;8096&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;FastEmby&lt;/h3&gt;
&lt;p&gt;用于接管 115 视频流量并实现重定向。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d \
  --name FastEmby \
  --network host \
  --restart unless-stopped \
  -e LICENSE_KEY=你的Symedia激活码 \
  -v /mnt/fastEmby/config:/app/config \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  shenxianmq/fastemby:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问端口：&lt;code&gt;9026&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;:::danger&lt;br /&gt;
所有容器部署完毕后，请务必回到虚空终端面板，将 &lt;strong&gt;TUN 模式&lt;/strong&gt; 开关关闭，避免影响服务器正常的网络流量转发。&lt;br /&gt;
:::&lt;/p&gt;
&lt;h2&gt;5. 在 CD2 和 OpenList 挂载网盘&lt;/h2&gt;
&lt;h3&gt;CloudDrive2 挂载&lt;/h3&gt;
&lt;p&gt;访问 CD2 后台，登录你的 115 和 123 网盘账号。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;115 网盘挂载名设为：&lt;code&gt;/115open&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;123 网盘挂载名设为：&lt;code&gt;/123云盘&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204154021-whz3qul.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入“挂载”，点击添加挂载点。按照下图填写，将网盘挂载到我们预设的 &lt;code&gt;/mnt/CloudNAS/CloudDrive&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204154131-l0cgl9v.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;挂载完成后，进入“我的文件”，找到网盘内的影视库文件夹，点击“详情”，将&lt;strong&gt;缓存时间修改为 0 秒&lt;/strong&gt;。如果后期遇到添加了影片，但是Symedia却找不到文件、无法链接的情况，可以点击黄色时钟按钮清空缓存。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204154451-30ciphv.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目录结构建议：&lt;/strong&gt;
为了配合 Symedia 的自动整理，建议按照如下树状结构组织你的网盘影视库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;115影视库
├── 电影
│   └── 上海堡垒 (2019)
│       ├── 上海堡垒 (2019) {tmdb-xxxxx} - 4K DV.mkv
│       └── (元数据)
├── 动漫
│   └── 笨女孩 (2017)
│       ├── (元数据)
│       └── Season 1
│           ├── (集元数据)
│           └── 笨女孩 (2017) S01E01 - 标题.mkv
├── 演唱会
└── 电视剧
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;OpenList 挂载&lt;/h3&gt;
&lt;p&gt;OpenList 主要用于解决 123 盘等非 115 网盘的直链分发。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全局设置&lt;/strong&gt;：进入设置 -&amp;gt; 全局，关闭“签名所有”。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20260204155039-ea1hwch.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;添加驱动&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进入存储 -&amp;gt; 添加。&lt;/li&gt;
&lt;li&gt;驱动选择：&lt;strong&gt;123开放平台&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;挂载路径：&lt;code&gt;/123云盘&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;WebDAV 策略：&lt;strong&gt;302 重定向&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;其他参数（客户端 ID、密钥等）：请参考 &lt;a href=&quot;https://doc.oplist.org/guide/drivers/123_open&quot;&gt;OpenList 官方文档 - 123开放平台驱动&lt;/a&gt; 获取并填写&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;6. 配置 Symedia&lt;/h2&gt;
&lt;h3&gt;6.1 插件配置：EmbyServer&lt;/h3&gt;
&lt;p&gt;进入 Symedia 插件中心，找到 EmbyServer。&lt;br /&gt;
按照下图填写 Emby 地址和 API Key。API Key 可以在 Emby 后台 -&amp;gt; API 页面生成。填写完毕记得保存。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204155657-4xf1kfj.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6.2 添加网盘账号&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;115 设置&lt;/strong&gt;：设置 -&amp;gt; 115设置 -&amp;gt; 添加配置 -&amp;gt; 随便填写名称 -&amp;gt; 保存。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20260204160016-e2mwzgy.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cookie 获取&lt;/strong&gt;：去插件中心安装“115扫码cookie”插件，扫码获取 Cookie 后，可以一键填入刚才创建的配置中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;123 设置&lt;/strong&gt;：设置 -&amp;gt; 123设置 -&amp;gt; 直接填写账号密码保存。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20260204160035-51gjzt4.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;6.3 STRM 链接同步&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;115 网盘同步任务：&lt;/strong&gt;
打开“链接同步”界面，创建任务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;媒体目录&lt;/strong&gt;：选择 CD2 挂载的 115 影视库目录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链接目录&lt;/strong&gt;：选择 &lt;code&gt;/media/影视库115&lt;/code&gt;（Symedia 容器内的路径）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204160209-v0h6k87.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20260204160406-5nzftja.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在“实时监控”选项卡中，选择 &lt;strong&gt;Webhook&lt;/strong&gt; 模式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204160443-0n3uyty.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;123 网盘同步任务：&lt;/strong&gt;
创建新任务，设置逻辑同上，但请注意下图红框标记的字段。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204160624-dzuk2x7.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6.4 连接 CD2 并开启 Webhook&lt;/h3&gt;
&lt;p&gt;Symedia 插件 -&amp;gt; clouddrive2助手。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;服务器地址&lt;/strong&gt;：填写 CD2 的访问地址（如果你的CD2是容器化的，IP填写服务器的内网IP，如果你按照本文来部署的，无需改动）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Token&lt;/strong&gt;：在 CD2 界面创建一个拥有**&amp;lt;u&amp;gt;所有权限&amp;lt;/u&amp;gt;**的 Token 填入。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;保存配置后，Symedia 就能接收到 CD2 的文件变动通知。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204160924-h8ri6so.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6.5 运行同步与归档&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;启动同步&lt;/strong&gt;：在链接同步界面，启动 115 和 123 的任务。可以在日志中查看进度。&lt;br /&gt;
如果卡住，可以在“CD2-系统任务-下载任务”查看情况，通常是由于下载到了体积较大的元数据或者多音轨。这是正常现象，如果嫌慢，可以编辑任务-高级配置-元数据后缀-去掉mka,mp3,flac等体积可能较大的文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置自动归档&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;创建规则&lt;/strong&gt;：Symedia -&amp;gt; 归档刮削 -&amp;gt; 规则。推荐使用“自动二级分类”，文件名保持默认。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建任务&lt;/strong&gt;：Symedia -&amp;gt; 归档刮削 -&amp;gt; 任务列表。源目录指向网盘内的“影视库待归档”文件夹，目标目录指向正式的“影视库”文件夹。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204162340-oa0csnq.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20260204162357-ffji1b1.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;设置完成后，只需将下载或转存的影片丢入“待归档”文件夹，Symedia 就会全自动刮削、重命名并移动到影视库，整个过程行云流水。&lt;/p&gt;
&lt;h2&gt;7. 配置 Emby 媒体库 + FastEmby&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Emby 媒体库设置&lt;/strong&gt;：&lt;br /&gt;
进入 Emby 设置 -&amp;gt; 媒体库。&lt;br /&gt;
将&lt;code&gt;/media&lt;/code&gt;​内的文件夹按你的喜好添加为媒体库，&lt;strong&gt;去掉一切&lt;/strong&gt;可能导致读取视频信息、生成缩略图有关的选项。之后开始扫库，体验速度带来的快感，所有人保持衣物干燥。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;FastEmby 设置&lt;/strong&gt;：&lt;br /&gt;
打开 FastEmby 后台，参照下图配置：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204161812-80of347.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;115 授权&lt;/strong&gt;：打开 Symedia 设置 -&amp;gt; 115设置，复制 Cookie。然后去 FastEmby -&amp;gt; 115助手，粘贴并测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;最终效果&lt;/strong&gt;：&lt;br /&gt;
客户端连接 &lt;code&gt;http://服务器IP:8098&lt;/code&gt;（FastEmby 的反代端口），即可实现 302 播放。Emby 负责展示精美的海报墙，FastEmby 负责在后台偷梁换柱。&lt;/p&gt;
&lt;p&gt;至此，新片转存后，Emby将会快速完成入库。emby由于会在播放前尝试解析视频信息，导致起播速度缓慢，可通过购买神医助手插件解决，详见本文开头部分。注意，这无法解决服务器流量消耗和配置不足、解析视频时卡死的问题。&lt;/p&gt;
&lt;h2&gt;8. 低配服务器的备用方案（极速起播）&lt;/h2&gt;
&lt;p&gt;如果服务器配置实在太低或者流量费用太高，可以将普通影片的播放任务完全交给 &lt;strong&gt;Infuse&lt;/strong&gt;，抛弃 Emby，或者只把Emby用于特殊影片的观看。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理变更：&lt;/strong&gt;
在备用方案中，Symedia 生成的 STRM 文件不再包含指向本地文件的路径，而是直接指向 OpenList 的网盘视频文件地址。Infuse 挂载 OpenList STRM文件夹后，请求视频时，OpenList 直接返回网盘直链（302 跳转），Infuse 随之直连网盘。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改造步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;清理旧数据&lt;/strong&gt;：&lt;br /&gt;
删除 &lt;code&gt;/mnt/media_strm&lt;/code&gt; 内的所有旧 STRM 文件（因为它们是给 Emby 用的本地路径）。&lt;br /&gt;
先预览要删除的文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find /mnt/media_strm -type f -name &quot;*.strm&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确认无误后，执行删除：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find /mnt/media_strm -type f -name &quot;*.strm&quot; -delete
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改同步任务&lt;/strong&gt;：&lt;br /&gt;
回到 Symedia -&amp;gt; 链接同步，修改 115 和 123 的任务配置。&lt;br /&gt;
重点关注红框部分，将链接前缀指向 OpenList 路径。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204163905-l4ohqtm.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;重新执行同步任务。这次速度会飞快，因为元数据之前已经下载过了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置 OpenList&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;添加本机存储&lt;/strong&gt;：&lt;br /&gt;
进入 OpenList -&amp;gt; 存储 -&amp;gt; 添加。&lt;br /&gt;
驱动：&lt;strong&gt;本机存储&lt;/strong&gt;。&lt;br /&gt;
挂载路径：&lt;code&gt;/media_strm&lt;/code&gt;​。&lt;br /&gt;
根文件夹路径：&lt;code&gt;/mnt/media_strm&lt;/code&gt;（即 Symedia 生成 STRM 的位置）。&lt;br /&gt;
保存。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204163619-a24kivu.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;添加 115 存储&lt;/strong&gt;：&lt;br /&gt;
驱动：&lt;strong&gt;115开放平台&lt;/strong&gt;。&lt;br /&gt;
挂载路径：&lt;code&gt;/115open&lt;/code&gt;​。&lt;br /&gt;
WebDAV 策略：&lt;strong&gt;302 重定向&lt;/strong&gt;。&lt;br /&gt;
令牌获取方式到OpenList官方文档去查&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Infuse 客户端设置&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加 &lt;strong&gt;WebDAV&lt;/strong&gt; 共享。&lt;/li&gt;
&lt;li&gt;地址：服务器 IP，端口 &lt;code&gt;5244&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用户名/密码：OpenList 的账号密码（默认是admin）。&lt;/li&gt;
&lt;li&gt;路径：&lt;code&gt;/dav/media_strm&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;关闭“预获取详情”和“预缓存海报”，但可以开启“智能文件夹”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;等待 Infuse 扫描完成，点击播放。此时，Infuse 获取到的是经过 OpenList 302 重定向后的直链，起播速度通常在 5 秒以内，拖拽进度条丝般顺滑。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20260204164342-3hrusoj.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>借助Nginx实现 NAS 服务内外网无缝访问</title><link>https://duke486.com/posts/%E5%80%9F%E5%8A%A9nginx%E5%AE%9E%E7%8E%B0-nas-%E6%9C%8D%E5%8A%A1%E5%86%85%E5%A4%96%E7%BD%91%E6%97%A0%E7%BC%9D%E8%AE%BF%E9%97%AE/</link><guid isPermaLink="true">https://duke486.com/posts/%E5%80%9F%E5%8A%A9nginx%E5%AE%9E%E7%8E%B0-nas-%E6%9C%8D%E5%8A%A1%E5%86%85%E5%A4%96%E7%BD%91%E6%97%A0%E7%BC%9D%E8%AE%BF%E9%97%AE/</guid><description>通过路由器 DNS 劫持和 Nginx 反向代理，实现 NAS 服务在内外网环境下的无缝访问，解决手动切换地址的问题。</description><pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;告别手动切换！实现 NAS 服务内外网无缝访问&lt;/h1&gt;
&lt;p&gt;自从组建了我的丐版 NAS 后，生活便利了不少。我在上面部署了 Komga 漫画库、Emby 媒体服务器、用于同步的 WebDAV 服务，以及 qBittorrent 下载器等。借助内网穿透，这些服务都能通过专属域名从公网访问。理论上，无论我身在何处，即使校园网宵禁，也能愉快地使用 NAS 上的资源。&lt;/p&gt;
&lt;p&gt;下面是我的网络拓扑示意图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20250409215554-5bqvsa8.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然而，一个恼人的问题始终困扰着我：&lt;br /&gt;
每当我在宿舍内网和外部网络环境之间切换时（比如从宿舍的 Wi-Fi 切换到手机热点或校园网），都必须手动更改各种客户端软件或网页书签中的 URL 地址。这意味着我不得不在 &lt;code&gt;192.168.5.123&lt;/code&gt;​ (内网 IP) 和 &lt;code&gt;mynas.com&lt;/code&gt;​ (我的公网域名，此处为示例) 之间反复横跳，才能重新连接服务。这涉及到手机上的文件管理器、相册同步工具、Emby 客户端等等。&lt;/p&gt;
&lt;p&gt;更令人抓狂的是，有些应用天生就不适合这种频繁切换。例如，Mihon 的 Komga 插件，一旦添加了某个源（漫画库），除非执行繁琐的手动迁移（而且只能一本一本地操作），否则根本无法更改其目标服务器地址。难道我为了能在内外网都能看漫画，需要在插件里把同一个漫画库添加两遍（一个内网 IP 版，一个公网域名版）吗？即便如此，版本冲突、阅读进度同步等新问题又会接踵而至。还有各类软件内置的 WebDAV 同步功能，通常只允许设置一个服务器地址，来回修改配置几乎不可能。&lt;/p&gt;
&lt;p&gt;这种不便极大地影响了我享受珍藏资料的热情。毕竟，谁愿意在想看电影、追漫画或读小说之前，先折腾一通服务器地址，然后焦急地等待加载圈转完呢？我们存储资源是为了欣赏，开启 WebDAV 是为了便捷同步，如果这些基本操作都变得费时费力，那 NAS 的乐趣何在？本文就将分享我如何解决这个问题，实现内外网访问的无缝切换。&lt;/p&gt;
&lt;h2&gt;我的网络环境&lt;/h2&gt;
&lt;p&gt;在开始之前，先明确一下我的网络状况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NAS 主机&lt;/strong&gt;: 运行在家庭内网，固定 IP 地址为 &lt;code&gt;192.168.5.123&lt;/code&gt;​。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内网访问&lt;/strong&gt;: NAS 上运行的各项服务（如 Emby, Komga 等）分别监听不同的端口，例如 &lt;code&gt;5001&lt;/code&gt;​, &lt;code&gt;5002&lt;/code&gt;​, &lt;code&gt;5003&lt;/code&gt;​ 等。在内网中，可以直接通过 &lt;code&gt;http://192.168.5.123:端口号&lt;/code&gt;​ 的方式访问这些服务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外网访问&lt;/strong&gt;: NAS 上运行了内网穿透客户端，将内网服务暴露到公网。可以通过 &lt;code&gt;https://mynas.com:外网端口号&lt;/code&gt;​ 的方式访问，其中 &lt;code&gt;mynas.com&lt;/code&gt;​ 是我的域名，&lt;code&gt;外网端口号&lt;/code&gt;​ 是由穿透服务商分配的公网端口（例如 &lt;code&gt;1234&lt;/code&gt;​）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心问题&lt;/strong&gt;: 内外网访问时，不仅 &lt;strong&gt;主机名&lt;/strong&gt; (&lt;code&gt;192.168.5.123&lt;/code&gt;​ vs &lt;code&gt;mynas.com&lt;/code&gt;​) 不同，&lt;strong&gt;端口号&lt;/strong&gt; (内网服务端口 vs 外网穿透端口) 和 &lt;strong&gt;协议/加密方式&lt;/strong&gt; (HTTP vs HTTPS) 也可能完全不同！&lt;/p&gt;
&lt;h2&gt;探索解决方案&lt;/h2&gt;
&lt;p&gt;为了解决这个地址统一性的问题，我进行了一些尝试，查阅了论坛资料，并与 AI 进行了探讨，初步筛选出以下几个看似可行的方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;异地组网工具 (如 Tailscale, ZeroTier, WireGuard 自建等)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;: 可以在外部网络环境下，通过虚拟局域网 IP 直接访问 NAS，似乎绕过了公网穿透。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;: 这些工具通常会分配 &lt;em&gt;新的&lt;/em&gt; 虚拟内网 IP (例如 &lt;code&gt;10.x.x.x&lt;/code&gt;​ 或 &lt;code&gt;172.x.x.x&lt;/code&gt;​)，而不是我原来的 &lt;code&gt;192.168.5.123&lt;/code&gt;​。这并没有解决需要统一访问地址的核心问题。此外，在国内网络环境下，某些服务的连接速度可能不尽人意。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;利用 DNS 服务商的解析记录&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;: 某些 DNS 服务商允许设置特殊的解析规则，可以将域名在特定条件下解析到内网 IP。理论上，在家时访问 &lt;code&gt;mynas.com&lt;/code&gt;​ 就能指向 &lt;code&gt;192.168.5.123&lt;/code&gt;​。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;: 这只能解决内网访问的问题。一旦离开内网，域名解析到的仍然是内网 IP，自然无法连接。更重要的是，它无法解决内外网端口不一致以及内网访问时 HTTPS 证书无效的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在路由器上设置 DNS 劫持/重定向&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;: 这似乎是最接近目标的方案。通过在路由器上设置规则，强制将 &lt;code&gt;mynas.com&lt;/code&gt;​ 在局域网内解析到 NAS 的内网 IP &lt;code&gt;192.168.5.123&lt;/code&gt;​。这样，无论在内网还是外网，访问 &lt;code&gt;mynas.com&lt;/code&gt;​ 时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在内网：DNS 查询被路由器拦截，返回 &lt;code&gt;192.168.5.123&lt;/code&gt;​。&lt;/li&gt;
&lt;li&gt;在外网：DNS 查询正常进行，返回公网 IP（穿透服务器的 IP）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;: 这个方案本身也存在两个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;端口不一致&lt;/strong&gt;: 内网服务端口（如 &lt;code&gt;5001&lt;/code&gt;​）和外网访问端口（如 &lt;code&gt;1234&lt;/code&gt;​）仍然不同。客户端依然需要根据网络环境切换端口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSL 证书问题&lt;/strong&gt;: 当在内网通过 &lt;code&gt;https://mynas.com:1234&lt;/code&gt;​ 访问时，虽然 DNS 解析到了 &lt;code&gt;192.168.5.123&lt;/code&gt;​，但浏览器/客户端请求的是 &lt;code&gt;mynas.com&lt;/code&gt;​ 的证书。如果直接访问 NAS 上的原始服务端口（如 &lt;code&gt;5001&lt;/code&gt;​），该服务很可能没有配置或配置了错误的 SSL 证书，导致连接失败或出现安全警告。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;综合来看，&lt;strong&gt;方案 3 (路由器 DNS 劫持)&lt;/strong&gt;  是最有潜力的，其核心思路正确——让域名在不同网络环境下指向不同的 IP。剩下的端口和 SSL 证书问题，可以通过在 NAS 上部署一个 &lt;strong&gt;Nginx 反向代理&lt;/strong&gt; 来完美解决。&lt;/p&gt;
&lt;h2&gt;实现过程&lt;/h2&gt;
&lt;p&gt;最终的实现步骤分为两大部分：路由器 DNS 配置和 NAS 上的 Nginx 反向代理配置。&lt;/p&gt;
&lt;h3&gt;1. 配置路由器 DNS 劫持&lt;/h3&gt;
&lt;p&gt;登录路由器管理界面，不同品牌路由器界面不同，找到 DNS 相关设置，通常被称为“静态主机名”、“DHCP/DNS”、“局域网 DNS”或类似名称。添加一条规则，将你的公网域名 &lt;code&gt;mynas.com&lt;/code&gt;​ 解析到 NAS 内网 IP &lt;code&gt;192.168.5.123&lt;/code&gt;​。&lt;/p&gt;
&lt;p&gt;设置完成后，当设备连接到这个路由器下的局域网时，任何对 &lt;code&gt;mynas.com&lt;/code&gt;​ 的 DNS 查询都会被路由器直接响应为 &lt;code&gt;192.168.5.123&lt;/code&gt;​。这样，访问 &lt;code&gt;https://mynas.com:1234&lt;/code&gt;​ 在内网实际上就是在访问 &lt;code&gt;https://192.168.5.123:1234&lt;/code&gt;​。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20250409222756-zo5w6v9.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
&lt;em&gt;(在 OpenWrt 路由器中设置 DNS 主机名)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2. 在 NAS 上配置 Nginx 反向代理&lt;/h3&gt;
&lt;p&gt;接下来解决端口和 SSL 证书问题。我们需要在 NAS 上安装 Nginx，并配置它来监听外网访问的端口（例如 &lt;code&gt;1234&lt;/code&gt;​），处理 &lt;code&gt;mynas.com&lt;/code&gt;​ 的 HTTPS 请求，然后将请求转发给 NAS 上实际运行服务的内网端口（例如 &lt;code&gt;5001&lt;/code&gt;​）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前提&lt;/strong&gt;: 确保你已经拥有 &lt;code&gt;mynas.com&lt;/code&gt;​ 域名的 SSL 证书文件（通常是 &lt;code&gt;.pem&lt;/code&gt;​ 或 &lt;code&gt;.crt&lt;/code&gt;​ 文件）和私钥文件（通常是 &lt;code&gt;.key&lt;/code&gt;​ 文件），并将它们上传到 NAS 的某个目录下。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤 1: 安装 Nginx&lt;/strong&gt; (假设 NAS 运行的是 Debian/Ubuntu 或类似的 Linux 系统)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 更新软件包列表
sudo apt update

# 安装 Nginx
sudo apt install nginx -y

# 检查 Nginx 服务状态 (应显示 active/running)
sudo systemctl status nginx
# 按 q 退出状态查看

# 设置 Nginx 开机自启 (通常安装后会自动设置，此命令确保启用)
sudo systemctl enable nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤 2: 存放 SSL 证书和私钥&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;建议将证书文件放在 Nginx 的标准配置目录下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建 SSL 证书存放目录 (如果不存在)
sudo mkdir -p /etc/nginx/ssl/

# 复制证书和私钥到指定位置
# 注意：请将 /path/to/your/certificate.pem 替换为你的证书文件实际路径
sudo cp /path/to/your/certificate.pem /etc/nginx/ssl/mynas.com.pem
# 注意：请将 /path/to/your/private.key 替换为你的私钥文件实际路径
sudo cp /path/to/your/private.key /etc/nginx/ssl/mynas.com.key

# 设置严格的权限，保护私钥（非常重要）
sudo chown root:root /etc/nginx/ssl/mynas.com.key
sudo chmod 600 /etc/nginx/ssl/mynas.com.key

# 证书文件权限可以相对宽松些
sudo chmod 644 /etc/nginx/ssl/mynas.com.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;提醒：如果你的证书文件后缀是&lt;/em&gt;  &lt;em&gt;​&lt;code&gt;.crt&lt;/code&gt;​&lt;/em&gt;​ &lt;em&gt;而不是&lt;/em&gt;  &lt;em&gt;​&lt;code&gt;.pem&lt;/code&gt;​&lt;/em&gt;​ &lt;em&gt;，请在后续 Nginx 配置中也使用&lt;/em&gt;  &lt;em&gt;​&lt;code&gt;.crt&lt;/code&gt;​&lt;/em&gt;​ &lt;em&gt;。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤 3: 备份默认 Nginx 配置 (可选但推荐)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup
sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.backup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤 4: 创建 Nginx 配置文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推荐在 &lt;code&gt;/etc/nginx/sites-available/&lt;/code&gt;​ 目录下为你的 NAS 服务创建一个新的配置文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nano /etc/nginx/sites-available/nas_proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤 5: 编写 Nginx 代理配置&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在打开的编辑器中，粘贴并修改以下配置。这个示例配置会让 Nginx 监听 &lt;code&gt;1234&lt;/code&gt;​ 端口上的 HTTPS 请求，并将流量转发到本机 (&lt;code&gt;127.0.0.1&lt;/code&gt;​) 的 &lt;code&gt;5001&lt;/code&gt;​ 端口 (假设这是 Emby 或 Komga 的内网端口)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 1234 ssl http2;        # 监听的外网端口 (需要与你外网访问时使用的端口一致)，启用 SSL 和 HTTP/2
    listen [::]:1234 ssl http2;   # 同时监听 IPv6 地址

    server_name mynas.com;         # 你的域名

    # SSL 证书配置
    ssl_certificate /etc/nginx/ssl/mynas.com.pem;      # 指向你的证书文件
    ssl_certificate_key /etc/nginx/ssl/mynas.com.key;    # 指向你的私钥文件

    # SSL 安全性设置 (推荐使用较新的协议)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off; # 通常设为 off 或 on 均可，现代浏览器协商能力强

    # 访问日志和错误日志 (自定义文件名，方便区分)
    access_log /var/log/nginx/mynas.com_1234.access.log;
    error_log /var/log/nginx/mynas.com_1234.error.log;

    location / {
        # 核心：反向代理配置
        # 将请求转发给实际的服务端口 (这里是 5001)
        # 如果 Nginx 和目标服务在同一台机器，使用 127.0.0.1 是最高效的
        proxy_pass http://127.0.0.1:5001;

        # 设置必要的 HTTP 头信息，以便后端服务能正确处理请求
        proxy_set_header Host $host; # 传递原始 Host 头
        proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实 IP
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 记录代理链 IP
        proxy_set_header X-Forwarded-Proto $scheme; # 告知后端是 http 还是 https

        # 支持 WebSocket (对 Emby, qBittorrent WebUI 等可能需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &quot;upgrade&quot;;
    }

    # 如果你需要代理其他服务到不同的外网端口，可以复制上面的 server {} 块，

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤 6: 启用新的配置文件并禁用默认配置&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/nas_proxy /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤 7: 测试 Nginx 配置并重启服务&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 测试配置文件语法是否有错误
sudo nginx -t

# 如果测试通过 (显示 syntax is ok 和 test is successful)，则重启 Nginx 服务
sudo systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤 8: 配置 NAS 防火墙&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;确保 NAS 的防火墙允许外部（在这里指局域网内部和潜在的公网）访问 Nginx 监听的端口（在我们的例子中是 &lt;code&gt;1234&lt;/code&gt;​ TCP 端口）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果你使用 &lt;code&gt;ufw&lt;/code&gt;​:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 允许访问 1234 端口
sudo ufw allow 1234/tcp
# 重新加载防火墙规则
sudo ufw reload
# 查看防火墙状态确认规则已添加
sudo ufw status
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你使用 &lt;code&gt;firewalld&lt;/code&gt;​:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 永久添加允许 1234 端口的规则
sudo firewall-cmd --permanent --add-port=1234/tcp
# 重新加载防火墙规则使之生效
sudo firewall-cmd --reload
# 列出所有规则确认
sudo firewall-cmd --list-all
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;步骤 9: 验证效果&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现在，无论你的设备连接的是家庭内网 Wi-Fi 还是外部网络（如手机流量、校园网），尝试通过 &lt;code&gt;https://mynas.com:1234&lt;/code&gt;​ 访问服务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;在内网&lt;/strong&gt;: 路由器 DNS 会将 &lt;code&gt;mynas.com&lt;/code&gt;​ 解析到 &lt;code&gt;192.168.5.123&lt;/code&gt;​。你的请求 &lt;code&gt;https://mynas.com:1234&lt;/code&gt;​ 实际上发送给了 &lt;code&gt;192.168.5.123&lt;/code&gt;​ 上的 Nginx。Nginx 使用正确的 SSL 证书响应，并将请求代理到内部的 &lt;code&gt;http://127.0.0.1:5001&lt;/code&gt;​。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在外网&lt;/strong&gt;: DNS 将 &lt;code&gt;mynas.com&lt;/code&gt;​ 解析到你的公网 IP（或穿透服务器的 IP）。请求 &lt;code&gt;https://mynas.com:1234&lt;/code&gt;​ 通过内网穿透隧道到直接达 NAS 上内部的 &lt;code&gt;http://127.0.0.1:5001&lt;/code&gt;​。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你应该能够在这两种网络环境下都成功访问服务，并且浏览器不会再报 SSL 证书错误。&lt;/p&gt;
&lt;h2&gt;最终效果&lt;/h2&gt;
&lt;p&gt;经过以上配置，我终于实现了 NAS 服务在内外网访问上的无缝切换！现在，所有的客户端软件（Emby, Komga 插件, 文件同步工具等）只需要设置 &lt;strong&gt;唯一&lt;/strong&gt; 的地址：&lt;code&gt;https://mynas.com:外网端口号&lt;/code&gt;​ (例如 &lt;code&gt;https://mynas.com:1234&lt;/code&gt;​)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;身处内网时&lt;/strong&gt;：请求会被路由器智能地导向 NAS 的内网 IP，数据直接在局域网内高速传输，即使外部网络中断（例如校园网断网）也完全不受影响。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;身处外网时&lt;/strong&gt;：请求则通过公网和内网穿透通道，同样能够无缝访问到 NAS 上的资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更重要的是，由于无论是内网（通过 Nginx 代理）还是外网访问，客户端收到的都是与 &lt;code&gt;mynas.com&lt;/code&gt;​ 域名匹配的有效 SSL 证书，因此浏览器或 App 不会再弹出“不安全”、“证书无效”或“服务器发送了无效响应”之类的恼人警告了。&lt;/p&gt;
&lt;p&gt;现在，我可以随时随地、无需任何手动干预，畅快地欣赏 NAS 上的漫画、电影，或者让相册、笔记等自动同步了。这才是 NAS 该有的便捷体验！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/Screenshot_20250409_224702_Emby-20250409224746-2rq8u8j.jpg&quot; alt=&quot;Screenshot_20250409_224702_Emby&quot; /&gt;&lt;br /&gt;
&lt;em&gt;(Emby 客户端现在只需一个地址就能稳定连接)&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>数据库课程笔记</title><link>https://duke486.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AF%BE%E7%A8%8B%E7%AC%94%E8%AE%B0mysql%E8%AF%AD%E6%B3%95/</link><guid isPermaLink="true">https://duke486.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AF%BE%E7%A8%8B%E7%AC%94%E8%AE%B0mysql%E8%AF%AD%E6%B3%95/</guid><description>系统性的数据库笔记，包含数据库的基本概念、数据模型、关系数据库等内容。重点记录了SQL语法、查询优化、索引等内容。</description><pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;课程信息&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;教材:&lt;/strong&gt;  《数据库系统概论》, 《Database System Concepts》（7th Edition）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作业:&lt;/strong&gt;   线上提交&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实验:&lt;/strong&gt;  内网地址上传&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;绪论&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据库 (Database)&lt;/strong&gt; : &lt;code&gt;合理存放、有关联、有结构的数据集合&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据库管理系统 (DBMS)&lt;/strong&gt; :&lt;br /&gt;
是&lt;code&gt;计算机的系统软件&lt;/code&gt;​;
位于&amp;lt;u&amp;gt;用户（应用程序）&amp;lt;/u&amp;gt;和&amp;lt;u&amp;gt;操作系统&amp;lt;/u&amp;gt;之间；&lt;br /&gt;
3个重要组成部分包括：数据定义功能 , 数据组织/存储/管理 (&lt;strong&gt;存储&lt;/strong&gt;引擎) , 数据存取功能 (&lt;strong&gt;查询&lt;/strong&gt;引擎) , 数据库运行管理功能 (&lt;strong&gt;事务&lt;/strong&gt;) , 数据库建立和维护功能 , 数据库的传输功能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据库系统 (DBS)&lt;/strong&gt; : &lt;strong&gt;组成&lt;/strong&gt;:&lt;br /&gt;
&lt;strong&gt;硬件, 软件&lt;/strong&gt; (&amp;lt;u&amp;gt;操作系统, 数据库管理系统&amp;lt;/u&amp;gt;, 应用开发工具, 高级语言及其编译系统), &lt;strong&gt;数据库&lt;/strong&gt; (工作数据, 数据字典), &lt;strong&gt;用户&lt;/strong&gt; (&amp;lt;u&amp;gt;数据库管理员&amp;lt;/u&amp;gt;, 系统分析员, 应用程序员, &amp;lt;u&amp;gt;终端用户&amp;lt;/u&amp;gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk1-20250409192758-a69roe9.jpg&quot; alt=&quot;sjk1-20250409192758-a69roe9&quot; /&gt;​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据库应用系统 (DBAS)&lt;/strong&gt; : &amp;lt;u&amp;gt;​&lt;code&gt;数据库系统&lt;/code&gt;​&amp;lt;/u&amp;gt;​&lt;code&gt;及其&lt;/code&gt;​&amp;lt;u&amp;gt;​&lt;code&gt;应用程序&lt;/code&gt;​&amp;lt;/u&amp;gt;​&lt;code&gt;的组成&lt;/code&gt;​; 也常被称为应用软件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据库系统的特点&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据整体结构化: ​&lt;strong&gt;​&lt;code&gt;与文件系统的根本区别&lt;/code&gt;​&lt;/strong&gt;​&lt;code&gt;，含数据本身和数据之间的整体结构化&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;数据的共享性高: &lt;code&gt;冗余度低，减少数据不一致性&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;数据的独立性高&lt;/li&gt;
&lt;li&gt;数据由DBMS统一管理和控制&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;数据模型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据模型应满足&lt;/strong&gt;:  能够真实模拟现实世界, 容易被人理解, 容易在计算机上实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;模型分类&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;概念模型&lt;/strong&gt;: &lt;strong&gt;E－R模型，UML模型&lt;/strong&gt;; &lt;code&gt;面向用户，主要用于数据库设计&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逻辑模型&lt;/strong&gt;: 层次模型，网状数据模型，关系数据模型，面向对象模型; &lt;code&gt;面向DBMS实现&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物理模型&lt;/strong&gt;: 描述数据在系统内部的表示和存取&lt;code&gt;，面向计算机系统&lt;/code&gt;​&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;模型的要素&lt;/strong&gt;: 数据**&amp;lt;u&amp;gt;结构&amp;lt;/u&amp;gt;​ &lt;strong&gt;, 数据&lt;/strong&gt;​&amp;lt;u&amp;gt;操作&amp;lt;/u&amp;gt;​ &lt;strong&gt;, 数据的&lt;/strong&gt;​&amp;lt;u&amp;gt;约束条件&amp;lt;/u&amp;gt;**&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;E－R模型&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;现实世界概念&lt;/strong&gt;: 实体，实体特性（属性），实体集，实体标识符&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信息世界概念&lt;/strong&gt;: 实体型，属性，实体，联系&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算机世界概念&lt;/strong&gt;: 记录型，字段，记录，码&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实体标识符&lt;/strong&gt;: &lt;code&gt;能够唯一标识一个实体的&lt;/code&gt;​&lt;strong&gt;​&lt;code&gt;最小的&lt;/code&gt;​&lt;/strong&gt;​&lt;code&gt;数据&lt;/code&gt;​&lt;strong&gt;​&lt;code&gt;集合&lt;/code&gt;​&lt;/strong&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实体（集）间的联系:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;二元联系&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一对一 (1:1)&lt;/strong&gt; : &lt;code&gt;实体集A中每个实体，B中至多一个实体与之联系，反之亦然&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一对多 (1:n)&lt;/strong&gt; : &lt;code&gt;实体集A中每个实体与B中任意个实体联系，B中每个实体至多和A中一个实体联系，有方向&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多对多 (m:n)&lt;/strong&gt; : &lt;code&gt;实体集A、B中每个实体都和另一个实体集中任意个实体联系&lt;/code&gt;​&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk3-20250409192834-1d3uqeu.jpg&quot; alt=&quot;SmartSelect_20250228_091149_Moon+ Reader Pro&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk4-20250409192842-af5mpxs.jpg&quot; alt=&quot;SmartSelect_20250228_091021_Moon+ Reader Pro&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk5-20250409192847-8exllh3.jpg&quot; alt=&quot;SmartSelect_20250228_091102_SiYuan&quot; /&gt;​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;多元联系&lt;/strong&gt;: &lt;code&gt;参与联系的实体集个数~3时，称为多元联系&lt;/code&gt;​;  如学生、教师、课程实体集之间的“教学“联系是三元联系&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自反联系&lt;/strong&gt;: &lt;code&gt;同一实体集内两部分实体之间的联系，特殊的二元联系&lt;/code&gt;​; 例如学生实体集的班长和同学&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;E－R图&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk6-20250409192853-ns1mts2.jpg&quot; alt=&quot;SmartSelect_20250228_091910_SiYuan&quot; /&gt;​&lt;/p&gt;
&lt;h3&gt;常用结构数据模型&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;层次模型&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;节点之间联系基本方式是1 : n&lt;/code&gt;​;  满足条件：只有一个根节点，根外节点只有一个双亲节点； 通过冗余数据、虚拟记录等手段也能表示多对多关系&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;: 查询效率高，结构简单，层次分明，便于实现&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;: 不适合表示非层次复杂联系，层次顺序严格增删限制多，查询复杂&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk7-20250409192859-lb1bjui.jpg&quot; alt=&quot;SmartSelect_20250228_092417_SiYuan&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;网状模型&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;取消了层次模型限制，允许一个以上节点无双亲，一个节点允许有一个以上双亲&lt;/code&gt;​;  记录型之间的联系通过“系&quot;(Set)实现；箭头指向 1 : n 联系的 n 方；多对多联系可用两个一对多联系实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk8-20250409192905-poq7b2d.jpg&quot; alt=&quot;SmartSelect_20250303_091902_Moon+ Reader Pro&quot; /&gt;​&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关系模型&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关系 (Relation) 的性质&lt;/strong&gt;:  属性不可分解，域应是原子数据集合；没有相同行列，行列顺序无关&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关系模式 (Relation Schema)&lt;/strong&gt; : &lt;code&gt;关系中信息内容结构的描述&lt;/code&gt;​&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;它包括关系名、属性名、每个属性列的取值集合、数据完整性约束条件以及各属性&lt;br /&gt;
间固有的数据依赖关系等。因此,关系模式可表示为:&lt;br /&gt;
$R(U,D, DOM,I,\Sigma)$&lt;/p&gt;
&lt;p&gt;其中:&lt;/p&gt;
&lt;p&gt;R是关系名;&lt;/p&gt;
&lt;p&gt;U是组成关系R的全部属性的集合;&lt;/p&gt;
&lt;p&gt;D是U中属性取值的值域;&lt;/p&gt;
&lt;p&gt;DOM 是属性列到域的映射,即$DOM: U \rightarrow D$,且每个属性$A_i$所有可能的取值集合构成&lt;br /&gt;
$D_i(i=1,2,...,n)$,并允许$D_i=D_j,i \neq j$;&lt;/p&gt;
&lt;p&gt;数据库原理与应用(MySQL版)&lt;/p&gt;
&lt;p&gt;I是一组完整性约束条件;&lt;/p&gt;
&lt;p&gt;$\Sigma$是属性集间的一组数据依赖。&lt;/p&gt;
&lt;p&gt;通常,在不涉及完整性约束及数据依赖的情况下,为了简化,可用R(U)表示关系模式。&lt;br /&gt;
例如,学生关系模式可表示为:&lt;/p&gt;
&lt;p&gt;S(学号,姓名,性别,年龄,学院)&lt;/p&gt;
&lt;p&gt;在层次和网状模型中,联系是用指针来实现的,而在关系模型中,联系是通过关系模式中的关键字来体现的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;面向对象模型&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;数据库系统结构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;三级模式结构&lt;/strong&gt;:&lt;br /&gt;
外部模式 (External Schema) / 视图层,&lt;br /&gt;
概念模式 (Conceptual Schema) / 模式层,&lt;br /&gt;
内部模式 (Internal Schema) / 存储层&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk9-20250409192912-cct4cj6.jpg&quot; alt=&quot;SmartSelect_20250304_082834_Moon+ Reader Pro&quot; /&gt;​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据独立性&lt;/strong&gt;: &lt;code&gt;应用程序和数据结构之间相互独立,不受影响&lt;/code&gt;​;  在三层模式体系结构中，指某一层次模式改变不影响上一层模式的能力&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;逻辑独立性&lt;/strong&gt;: 模式变化, 外模式/应用程序无须改变; &lt;code&gt;外模式/模式映像保证逻辑独立性&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物理独立性&lt;/strong&gt;: 內模式改变，概念模式无需改变; &lt;code&gt;模式/内模式映像保证物理独立性&lt;/code&gt;​&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;外模式／模式映像？模式／内模式映像？&lt;/strong&gt;  [需要人工补充]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用户通过DBMS访问数据库的过程&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./assets/SmartSelect_20250304_090228_SiYuan-20250304090234-jneyg77.jpg&quot; alt=&quot;SmartSelect_20250304_090228_SiYuan&quot; /&gt;​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DBMS 的工作过程&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;接受应用程序的数据请求&lt;/li&gt;
&lt;li&gt;DBMS 分析用户操作请求&lt;/li&gt;
&lt;li&gt;DBMS 向操作系统发操作请求&lt;/li&gt;
&lt;li&gt;操作系统处理数据, 结果送系统缓冲区, 发读完标志&lt;/li&gt;
&lt;li&gt;DBMS 接信号, 缓冲区数据经模式映射变用户逻辑记录送用户区,  给用户成功/失败信息&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk10-20250409192917-90moovc.jpg&quot; alt=&quot;SmartSelect_20250304_090449_Moon+ Reader Pro&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;关系数据库&lt;/h2&gt;
&lt;h3&gt;关系模型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;域 (Domain)&lt;/strong&gt; : &lt;code&gt;一组相同数据类型的值的集合&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;笛卡尔积&lt;/strong&gt;:$D_1 \times D_2 \times \cdots \times D_n = {(d_1, d_2, \dots, d_n)| d_i \in D_i, i=1,2,\dots,n}$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关系 (Relation)&lt;/strong&gt; : &lt;code&gt;一组域的笛卡尔积的子集&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;码 (Key)&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;候选码 (Candidate Key)&lt;/strong&gt; : &lt;code&gt;关系中唯一标识元组的属性或**最小属性集**&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主码 (Primary Key)&lt;/strong&gt; : &lt;code&gt;选择一个候选码为主码&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主属性 (Prime Attribute)&lt;/strong&gt; : &lt;code&gt;包含在任何一个候选码中的属性&lt;/code&gt;​;  剩下的都是非主属性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;外码 (Foreign Key)&lt;/strong&gt; : &lt;code&gt;基本关系 R 的属性F (非R的码)，关系S的主码Ks，若F与Ks对应, 则F是R的外码, R为参照关系, S 为被参照关系&lt;/code&gt;​&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;例如&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学生表：&lt;code&gt;学生（学生学号, 姓名, 性别, 出生年份, 专业编号, 身份证号码）&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;专业表：&lt;code&gt;专业（专业编号, 专业名称, 专业负责人）&lt;/code&gt;​&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;学生表主码&quot;学生学号&quot;, 专业表主码&quot;专业编号&quot;, 学生表&quot;专业编号&quot;参照专业表专业取值,  &quot;专业编号&quot;是学生表外码； &quot;专业编号&quot;作为专业表主码和学生表外码, 实现两表关联； 表的联系通过公共属性实现，公共属性是一个表的主码和另一表的外码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;&amp;lt;span data-type=&quot;text&quot; style=&quot;font-size: 1.38em; font-weight: 600; font-family: var(--b3-font-family-protyle); background-color: var(--b3-theme-background); color: var(--b3-theme-on-background);&quot;&amp;gt;关系的性质&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;h3&gt;关系的性质&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;列是同质的：每列数据类型相同，来自同一个域。&lt;/li&gt;
&lt;li&gt;不同的列可以有相同的域。&lt;/li&gt;
&lt;li&gt;行列顺序无所谓。&lt;/li&gt;
&lt;li&gt;任意两行的候选码不能相同。&lt;/li&gt;
&lt;li&gt;每个分量必须是不可分的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;关系操作&lt;/h3&gt;
&lt;p&gt;关系操作分为： &lt;strong&gt;检索&lt;/strong&gt;、&lt;strong&gt;更新&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;更新操作分为：&lt;strong&gt;插入&lt;/strong&gt;、&lt;strong&gt;删除&lt;/strong&gt;、&lt;strong&gt;修改&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;关系数据库管理系统一般向用户提供4种数据操纵：数据检索，插入，删除，修改。&lt;/p&gt;
&lt;p&gt;操纵的对象是关系。基本操纵方式有5种：属性指定，元组选择，关系合并，元组插入，元组删除。&lt;/p&gt;
&lt;h3&gt;关系完整性约束&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实体完整性规则&lt;/strong&gt;：主属性不能取空值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参照完整性规则&lt;/strong&gt;：外码必须是被参照关系中主码的有效值，或者是一个空值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户定义完整性规则&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;关系代数&lt;/h2&gt;
&lt;h3&gt;基本关系运算&lt;/h3&gt;
&lt;p&gt;基本关系运算：&lt;code&gt;五种基本关系运算&lt;/code&gt;​（选择，投影，并，差，笛卡尔积）&lt;/p&gt;
&lt;h3&gt;传统的集合运算&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;并运算 (UNION)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;差运算 (DIFFERENCE)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;交运算 (INTERSECTION)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;广义笛卡尔积运算 (CARTESIAN PRODUCT)&lt;/strong&gt; ：R中的每一个元组与S的每一个元组串接。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新关系的度：R的度 + S的度&lt;/li&gt;
&lt;li&gt;基数：R的基数 × S的基数&lt;/li&gt;
&lt;li&gt;新关系的同名属性：使用 &quot;关系名.属性名&quot; 区分。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意区分：逻辑运算 &lt;code&gt;V（或）&lt;/code&gt;​ 和关系运算 &lt;code&gt;U（并）&lt;/code&gt;​&lt;/p&gt;
&lt;h3&gt;专门的关系运算&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;选择 (Selection)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;选择是指在给定的关系中选择出满足条件的元组组成一个新的关系。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关系R关于公式F的选择运算用$\sigma_F(R)$表示,形式&lt;br /&gt;
化定义如下:&lt;br /&gt;
​$\sigma_F(R) = {t | t \in R \land F(t) = &apos;true&apos;}$&lt;br /&gt;
其中,$\sigma_F(R)$表示从R中挑选满足公式F的元组所构成的关系。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个式子的左侧看起来是这样的：&lt;br /&gt;
$\sigma_{出生年份&amp;gt;=1990 \land 出生年份&amp;lt;=1999}$(学生)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;投影 (Projection)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对一个关系内属性的指定称为投影运算，它也是单目运算。这个操作是对一个关系进行垂直分割，消去某些列，并按要求的顺序重新排列，再&lt;strong&gt;删除重复元组&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关系R关于属性集A的投影运算用$\prod_A(R)$表示,形式化定义如下:&lt;/p&gt;
&lt;p&gt;$\prod_A(R) = {t[A] | t \in R }$&lt;/p&gt;
&lt;p&gt;【例】查询所有学生的姓名和籍贯。&lt;/p&gt;
&lt;p&gt;分析：该操作要进行的操作对象为学生表，所关心的属性只有姓名和籍贯。&lt;/p&gt;
&lt;p&gt;表示为：&lt;/p&gt;
&lt;p&gt;$\prod_{姓名,籍贯}$(学生)或者$\prod_{2,4}$(学生),属性名可用列号来表示。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连接 (Join)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;连接运算是把两个关系连接成一个新关系，它是一个双目运算。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;假设有两个关系R和S,R和S的连接是指在R与S的笛卡尔积中,选取R中的属性组A的值与S中的属性组B的值进行比较后,找出满足比较关系θ的元组,组成一个新的关系。连接操作可以记作:&lt;/p&gt;
&lt;p&gt;$R \underset{A\theta B}{\bowtie} S = {t_r t_s | t_r \in R \land t_s \in S \land t_r[A]\theta t_s[B]}$&lt;/p&gt;
&lt;p&gt;其中,A和B分别为R和S上度数相等且可比的属性组。θ是比较运算符,因此,也称连接为θ连接。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;关系可以自身连接。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自然连接 (Natural Join)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它要求两个关系中进行比较的分量必须是&lt;strong&gt;相同的属性组&lt;/strong&gt;，并且在结果中把重复的属性列&lt;strong&gt;去掉&lt;/strong&gt;。如果关系R和S具有相同的属性组B，则自然连接可记为：&lt;/p&gt;
&lt;p&gt;$R \bowtie S = {t_r t_s | t_r \in R \land t_s \in S \land t_r[B] = t_s[B] }$&lt;/p&gt;
&lt;p&gt;具体计算过程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算 R × S (笛卡尔积)。&lt;/li&gt;
&lt;li&gt;设R和S的公共属性是B，找出 R×S 中满足R中属性B的值与S中的属性B的值相等的那些元组。&lt;/li&gt;
&lt;li&gt;去掉S中B列（或者去掉R中B列）。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;除运算 (Division)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;适用的场合：查询条件是一个集合包含另一个集合，即适合于含有短语 “对所有的…” 的查询，如 “查询被所有的学生都选修的课程的信息”。&lt;/p&gt;
&lt;p&gt;除运算的条件：假设给定关系 R(X, Y) 和 S(Y, Z)。 R和S中的Y可以有不同的属性名，但必须出自相同的域集。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;除运算的结果:R与S的除运算得到一个新的关系P(X)，P是R中满足下列条件的元组在X属性列上的投影:元组在X上分量值x的像集Yₓ,包含S在Y上投影的集合。&lt;br /&gt;
记作:&lt;/p&gt;
&lt;p&gt;$R÷S = {t_x[X]\ |\ t_r \in R \land \prod_y(S) \subseteq Y_x }$&lt;/p&gt;
&lt;p&gt;其中,Yₓ为x在R中的像集,像集Yₓ={t[Y]|t∈ R,t[X]=x}。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;查询优化&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;10种公式&lt;/strong&gt;：[需要人工补充]&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化策略：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在关系代数表达式中尽可能&lt;strong&gt;早执行选择&lt;/strong&gt;操作。&lt;/li&gt;
&lt;li&gt;合并笛卡尔积和其后的选择操作，使之成为一个连接运算。&lt;/li&gt;
&lt;li&gt;合并连续的&lt;strong&gt;选择和投影&lt;/strong&gt;操作，以免分开运算造成多次扫描文件，从而节省了操作时间。&lt;/li&gt;
&lt;li&gt;找出表达式里的公共子表达式。如果一个表达式中多次出现某个子表达式，那么应该将该子表达式预先计算出结果保存起来，以避免重复计算。&lt;/li&gt;
&lt;li&gt;适当地对关系文件做预处理。根据实际需要对文件进行分类排序或建立临时索引等。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;优化算法&lt;/strong&gt;：一个关系代数表达式，可以得到一棵语法树，叶子是关系，非叶子节点是关系代数操作。利用前面的等价变换规则和优化策略来对关系代数表达式进行优化。&lt;/p&gt;
&lt;h3&gt;关系系统&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：一个系统可定义为关系系统，当且仅当它支持：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;关系数据结构。&lt;/li&gt;
&lt;li&gt;支持选择、投影和（自然）连接运算，并且对这些运算不必要求定义任何物理存取路径。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;分类&lt;/strong&gt;：表式系统（不算关系系统），最小关系系统，完备关系系统，全关系系统。&lt;/p&gt;
&lt;h2&gt;SQL&lt;/h2&gt;
&lt;h3&gt;特点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;高度非过程化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;功能完备并一体化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;统一的语法结构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自含式&lt;/li&gt;
&lt;li&gt;嵌入式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;语言简洁，易学易用&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;体系结构&lt;/h3&gt;
&lt;p&gt;SQL支持关系数据库体系结构，即外模式、模式和内模式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/sjk11-20250409192926-ssvkqaz.jpg&quot; alt=&quot;SmartSelect_20250317_081839_SiYuan&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;SQL功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;表结构的定义&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE &amp;lt;表名&amp;gt;
(
    &amp;lt;列名&amp;gt; &amp;lt;数据类型&amp;gt; [列级完整性约束条件],
    [, &amp;lt;列名&amp;gt; &amp;lt;数据类型&amp;gt; [列级完整性约束条件] ... ],
    [, &amp;lt;表级完整性约束条件&amp;gt;]
)[表属性，例如： ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci]

--指定存储引擎为 InnoDB，支持事务、行级锁和外键约束，适合处理高并发的事务场景--
--设置字符集为 utf8mb4，支持 UTF-8 编码并允许存储 4 字节字符（如 emoji 表情）；--
--指定排序规则为不区分大小写的 Unicode 排序，用于排序和比较字符数据--
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在语法描述中，中括号 &lt;code&gt;[]&lt;/code&gt;​ 用于表示可选的语法元素。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;常见约束条件：&lt;code&gt;NOT NULL&lt;/code&gt;​、&lt;code&gt;UNIQUE&lt;/code&gt;​、&lt;code&gt;DEFAULT&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;AUTO_INCREMENT&lt;/code&gt;​ 必须与被索引的列一起使用（通常是主键或唯一键），但它本身并不强制要求该列必须是主键。选择将其用于主键通常是出于设计和管理的便利性&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;employee_id INT PRIMARY KEY AUTO_INCREMENT,&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;每次插入新记录时 employee_id 值自动加 1&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;CHECK&lt;/code&gt;​ ：约束条件，例如确保薪资为非负数，防止负薪资的插入。&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;salary DECIMAL(10, 2) CHECK (salary &amp;gt;= 0)&lt;/code&gt;​&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;查看表结构/查看建表语句：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DESCRIBE &amp;lt;表名&amp;gt;
SHOW CREATE TABLE &amp;lt;表名&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;主要数据类型：&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1. 数值类型&lt;/h3&gt;
&lt;p&gt;数值类型用于存储数值数据，根据数值的大小和精度，数值类型可分为整数类型和浮点类型。&lt;/p&gt;
&lt;h4&gt;1.1 整数类型&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据类型&lt;/th&gt;
&lt;th&gt;字节数&lt;/th&gt;
&lt;th&gt;范围（有符号）&lt;/th&gt;
&lt;th&gt;范围（无符号）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TINYINT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;-128 到 127&lt;/td&gt;
&lt;td&gt;0 到 255&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMALLINT&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;-32,768 到 32,767&lt;/td&gt;
&lt;td&gt;0 到 65,535&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEDIUMINT&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;-8,388,608 到 8,388,607&lt;/td&gt;
&lt;td&gt;0 到 16,777,215&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INT&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;-2,147,483,648 到 2,147,483,647&lt;/td&gt;
&lt;td&gt;0 到 4,294,967,295&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BIGINT&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807&lt;/td&gt;
&lt;td&gt;0 到 18,446,744,073,709,551,615&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TINYINT&lt;/strong&gt;：适合存储较小的整数，例如布尔值或状态码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INT&lt;/strong&gt; 和 &lt;strong&gt;BIGINT&lt;/strong&gt;：适合较大数值的存储，如计数、金额等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;1.2 浮点类型与定点类型&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据类型&lt;/th&gt;
&lt;th&gt;字节数&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FLOAT&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;单精度浮点数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DOUBLE&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;双精度浮点数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DECIMAL(m, d)&lt;/td&gt;
&lt;td&gt;可变&lt;/td&gt;
&lt;td&gt;定点数，精确存储&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FLOAT&lt;/strong&gt; 和 &lt;strong&gt;DOUBLE&lt;/strong&gt;：适合对精度要求不高的科学计算和分析。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DECIMAL&lt;/strong&gt;：适合精度要求高的场景，如货币计算。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 字符串类型&lt;/h3&gt;
&lt;p&gt;字符串类型用于存储文本数据，根据字符长度和内容，MySQL 提供了多种字符串类型。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据类型&lt;/th&gt;
&lt;th&gt;字节数&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CHAR(n)&lt;/td&gt;
&lt;td&gt;n 字节&lt;/td&gt;
&lt;td&gt;固定长度字符串，适合短文本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VARCHAR(n)&lt;/td&gt;
&lt;td&gt;1 + 实际字符长度&lt;/td&gt;
&lt;td&gt;可变长度字符串，适合长文本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TEXT&lt;/td&gt;
&lt;td&gt;实际字符长度&lt;/td&gt;
&lt;td&gt;大文本数据，最大 64KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLOB&lt;/td&gt;
&lt;td&gt;实际字符长度&lt;/td&gt;
&lt;td&gt;二进制大对象，最大 64KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CHAR&lt;/strong&gt;：适合固定长度的短字符串，如性别、国家代码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VARCHAR&lt;/strong&gt;：适合变长字符串，如用户名、电子邮件地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TEXT&lt;/strong&gt; 和 &lt;strong&gt;BLOB&lt;/strong&gt;：适合较大文本或二进制数据，如文章内容或图像数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 日期和时间类型&lt;/h3&gt;
&lt;p&gt;日期和时间类型用于存储日期和时间信息，MySQL 提供了灵活的时间数据类型。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据类型&lt;/th&gt;
&lt;th&gt;字节数&lt;/th&gt;
&lt;th&gt;范围&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DATE&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1000-01-01 到 9999-12-31&lt;/td&gt;
&lt;td&gt;存储日期（年-月-日）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIME&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;-838:59:59 到 838:59:59&lt;/td&gt;
&lt;td&gt;存储时间（时:分:秒）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DATETIME&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1000-01-01 00:00:00 到 9999-12-31 23:59:59&lt;/td&gt;
&lt;td&gt;存储日期和时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIMESTAMP&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1970-01-01 00:00:00 到 2038-01-19 03:14:07 UTC&lt;/td&gt;
&lt;td&gt;存储日期和时间，适合时间戳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YEAR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1901 到 2155&lt;/td&gt;
&lt;td&gt;存储年&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DATE&lt;/strong&gt; 和 &lt;strong&gt;DATETIME&lt;/strong&gt;：适合存储日期和完整的日期时间信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIMESTAMP&lt;/strong&gt;：适合记录 Unix 时间戳。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIME&lt;/strong&gt;：适合记录时间数据，如工时或时长。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. JSON 类型&lt;/h3&gt;
&lt;p&gt;MySQL 5.7 及以上版本支持 JSON 数据类型，允许存储 JSON 格式的数据。JSON 类型字段可用于存储灵活的结构化数据，但要注意 JSON 类型的字段索引和查询性能较差，不宜用于复杂查询。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE example_table (
    data JSON
);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：适合存储结构变化频繁的数据，如动态配置或用户偏好。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. ENUM 和 SET 类型&lt;/h3&gt;
&lt;h4&gt;ENUM&lt;/h4&gt;
&lt;p&gt;​&lt;code&gt;ENUM&lt;/code&gt;​ 数据类型用于定义一组允许的值，例如状态或类型。创建 &lt;code&gt;ENUM&lt;/code&gt;​ 列时指定所有可能值：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE example_table (
    status ENUM(&apos;active&apos;, &apos;inactive&apos;, &apos;pending&apos;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;SET&lt;/h4&gt;
&lt;p&gt;​&lt;code&gt;SET&lt;/code&gt;​ 数据类型用于定义多个可能值的组合，适合多选项字段。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE example_table (
    tags SET(&apos;sports&apos;, &apos;news&apos;, &apos;entertainment&apos;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 选择数据类型的注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;空间效率&lt;/strong&gt;：尽量选择占用空间少的数据类型，能用 &lt;code&gt;TINYINT&lt;/code&gt;​ 就不选 &lt;code&gt;INT&lt;/code&gt;​。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;性能考虑&lt;/strong&gt;：使用合适的数据类型有助于提高查询和索引性能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;存储精度&lt;/strong&gt;：财务数据用 &lt;code&gt;DECIMAL&lt;/code&gt;​ 存储，避免浮点数精度误差。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;拓展性&lt;/strong&gt;：&lt;code&gt;VARCHAR&lt;/code&gt;​ 灵活适合变长字符，避免固定长度导致浪费空间&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://www.quanxiaoha.com/mysql/mysql-data-type.html&quot;&gt;小哈教程-mySQL数据类型&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主关键字定义 (Primary Key Definition)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;关系数据库中，唯一标识每一行数据的属性或属性组合。&lt;br /&gt;
每个表只能有一个主键。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;声明方式：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单属性主键：&lt;/strong&gt;   &lt;code&gt;属性名 数据类型 PRIMARY KEY&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通用方式：&lt;/strong&gt;   &lt;code&gt;PRIMARY KEY (属性1, 属性2, ...)&lt;/code&gt;​ (可用于单属性或多属性主键)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;方法一:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE  TABLE  学生
(学号    CHAR(8)     PRIMARY KEY,
姓名    CHAR(8),
性别    CHAR(2),
出生年份  SMALLINT,
籍贯    CHAR(8),
学院    CHAR(15));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;方法二:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE  TABLE  学生
(学号    CHAR(8),
姓名    CHAR(8),
性别    CHAR(2),
出生年份  SMALLINT,
籍贯    CHAR(8),
学院    CHAR(15),
PRIMARY  KEY(学号));
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;外键定义 (Foreign Key Definition)&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;如果外部关键字只有一个属性，可以在它的属性名和类型后面直接用 &lt;code&gt;&quot;REFERENCES&quot;&lt;/code&gt;​ 说明它参照了某个表的某些属性（必须是主关键字）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REFERENCES &amp;lt;表名&amp;gt;(&amp;lt;属性&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 &lt;code&gt;CREATE TABLE&lt;/code&gt;​ 语句的属性列表后面增加一个或几个外部关键字说明，其格式为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FOREIGN KEY(&amp;lt;属性 1&amp;gt;) REFERENCES &amp;lt;表名&amp;gt;(&amp;lt;属性2&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE 学习
(学号 CHAR(8),
课程号 CHAR(8),
成绩 SMALLINT,
PRIMARY KEY (学号,课程号),
FOREIGN KEY (学号) REFERENCES 学生(学号)
FOREIGN KEY (课程) REFERENCES 课程(课程号)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;基本表修改和删除&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;调整列位置：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;AFTER&lt;/code&gt;​ 或 &lt;code&gt;FIRST&lt;/code&gt;​ 关键字调整列的位置。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;AFTER column_name&lt;/code&gt;​: 将列移动到指定列之后。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;FIRST&lt;/code&gt;​: 将列移动到第一列。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;-- 将 email 列移动到 name 列之后
ALTER TABLE t_employee
MODIFY email VARCHAR(255) AFTER name;

-- 将 email 列移动到第一列
ALTER TABLE t_employee
MODIFY email VARCHAR(255) FIRST;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;修改：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE &amp;lt;表名&amp;gt;
[ ADD [COLUMN] &amp;lt;新列名&amp;gt; &amp;lt;数据类型&amp;gt; [DEFAULT 默认值] [完整性约束] ]
| [ DROP [COLUMN] &amp;lt;列名&amp;gt; ]
| [ ALTER COLUMN &amp;lt;列名&amp;gt; &amp;lt;数据类型&amp;gt; ]
| [ ADD CONSTRAINT &amp;lt;约束名&amp;gt; &amp;lt;约束类型&amp;gt; (&amp;lt;列名&amp;gt;)]
| [ DROP CONSTRAINT &amp;lt;约束名&amp;gt; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常用操作:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;增加列 (ADD COLUMN):&lt;/p&gt;
&lt;p&gt;语法: &lt;code&gt;ALTER TABLE &amp;lt;表名&amp;gt; ADD [COLUMN] &amp;lt;新列名&amp;gt; &amp;lt;数据类型&amp;gt; [DEFAULT 默认值] [完整性约束]&lt;/code&gt;​&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 学生 ADD COLUMN 年龄 SMALLINT;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除列 (DROP COLUMN):&lt;/p&gt;
&lt;p&gt;语法: &lt;code&gt;ALTER TABLE &amp;lt;表名&amp;gt; DROP [COLUMN] &amp;lt;列名&amp;gt;&lt;/code&gt;​&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 学生 DROP COLUMN 年龄;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改列 (ALTER COLUMN / MODIFY COLUMN / CHANGE COLUMN):&lt;/p&gt;
&lt;p&gt;标准 SQL使用 &lt;code&gt;ALTER COLUMN&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;语法: &lt;code&gt;ALTER TABLE &amp;lt;表名&amp;gt; ALTER COLUMN &amp;lt;列名&amp;gt; &amp;lt;数据类型&amp;gt;&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;修改  &lt;code&gt;课程名&lt;/code&gt;​ 列类型为 &lt;code&gt;VARCHAR(20)&lt;/code&gt;​:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 课程 ALTER COLUMN 课程名 VARCHAR(20);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MySQL:  推荐使用 &lt;code&gt;MODIFY COLUMN&lt;/code&gt;​ 或 &lt;code&gt;MODIFY&lt;/code&gt;​， &lt;code&gt;CHANGE COLUMN&lt;/code&gt;​  还可以同时修改列名。&lt;/p&gt;
&lt;p&gt;修改类型: &lt;code&gt;ALTER TABLE &amp;lt;表名&amp;gt; MODIFY [COLUMN] &amp;lt;列名&amp;gt; &amp;lt;新数据类型&amp;gt; [其他属性]&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;修改名和类型 : &lt;code&gt;ALTER TABLE &amp;lt;表名&amp;gt; CHANGE [COLUMN] &amp;lt;旧列名&amp;gt; &amp;lt;新列名&amp;gt; &amp;lt;新数据类型&amp;gt; [其他属性]&lt;/code&gt;​&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE t_employee MODIFY email TINYINT;
ALTER TABLE t_employee CHANGE email employee_email TINYINT;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;增加约束 (ADD CONSTRAINT):&lt;/strong&gt;   向表添加约束，如主键、外键、唯一约束等。&lt;/p&gt;
&lt;p&gt;语法: &lt;code&gt;ALTER TABLE &amp;lt;表名&amp;gt; ADD CONSTRAINT &amp;lt;约束名&amp;gt; &amp;lt;约束类型&amp;gt; (&amp;lt;列名&amp;gt;)&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;学生表添加主键约束 &lt;code&gt;PK_学生&lt;/code&gt;​，列为 &lt;code&gt;学号&lt;/code&gt;​:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 学生 ADD CONSTRAINT PK_学生 PRIMARY KEY (学号);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;删除约束 (DROP CONSTRAINT):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;语法: &lt;code&gt;ALTER TABLE &amp;lt;表名&amp;gt; DROP CONSTRAINT &amp;lt;约束名&amp;gt;&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;示例: 学生表删除主键约束 &lt;code&gt;PK_学生&lt;/code&gt;​:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 学生 DROP CONSTRAINT PK_学生;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;列长度修改:&lt;/strong&gt;  增加列长度通常没问题，&lt;strong&gt;但缩短长度可能导致数据丢失或失败&lt;/strong&gt;，确保新长度能容纳现有数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NULL 约束修改为 NOT NULL:&lt;/strong&gt;   若将列从允许 &lt;code&gt;NULL&lt;/code&gt;​ 改为 &lt;code&gt;NOT NULL&lt;/code&gt;​，&lt;strong&gt;必须先处理列中的&lt;/strong&gt; &lt;strong&gt;​&lt;code&gt;NULL&lt;/code&gt;​&lt;/strong&gt;​ &lt;strong&gt;值&lt;/strong&gt;，否则修改会失败。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;删除 (Delete)&lt;/strong&gt; ：&lt;code&gt;DROP TABLE [IF EXISTS] &amp;lt;表名&amp;gt; [RESTRICT | CASCADE];&lt;/code&gt;​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;RESTRICT&lt;/code&gt;​：如果有视图或约束条件涉及要删除的表时，禁止DBMS执行该命令。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;CASCADE&lt;/code&gt;​：将该表与其涉及的对象一起删除。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;IF EXISTS&lt;/code&gt;​：表示如果表存在则删除，否则不会报错。建议使用此选项以避免因表不存在而导致的错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;锁表问题&lt;/strong&gt;&lt;br /&gt;
​&lt;code&gt;ALTER TABLE&lt;/code&gt;​ 操作会锁定表结构，因此在高并发环境中应小心操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;列的删除与重命名&lt;/strong&gt;&lt;br /&gt;
删除列或重命名操作可能会导致依赖该列的视图或存储过程失效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;操作影响&lt;/strong&gt;&lt;br /&gt;
某些复杂的 &lt;code&gt;ALTER TABLE&lt;/code&gt;​ 操作可能需要 MySQL 创建临时表，从而耗费更多资源。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h3&gt;索引建立与删除&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;建立索引 (Create Index)&lt;/strong&gt; ：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE [UNIQUE] [CLUSTER] INDEX &amp;lt;索引名&amp;gt;
ON &amp;lt;表名&amp;gt;(&amp;lt;列名&amp;gt; [&amp;lt;次序&amp;gt;][, &amp;lt;列名&amp;gt; [&amp;lt;次序&amp;gt;] ...]);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;UNIQUE 表明此索引的每个索引值只对应唯一的数据记录。CLUSTER 表示要建立的索引是聚簇索引。聚簇索引是指索引项的顺序与表中记录的物理顺序一致的索引组织。&lt;br /&gt;
【例3-10】 为学生、课程和学习表建立索引。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE UNIQUE INDEX STU_IDX_SNO ON 学生(学号);
CREATE UNIQUE INDEX COU_IDX_CNO ON 课程(课程号);
CREATE UNIQUE INDEX SC_IDX_SNO_CNO ON 学习(学号 ASC,课程号 DESC);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中,ASC表示按照升序排列,DESC表示按照降序排列。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;删除索引 (Drop Index)&lt;/strong&gt; ：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DROP INDEX &amp;lt;索引名&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据查询&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;语法格式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SQL 92 标准中SELECT语句的语法格式:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT [ALL | DISTINCT] &amp;lt;属性列表&amp;gt;
FROM &amp;lt;表名或视图名&amp;gt;[,&amp;lt;表名或视图名&amp;gt;]...
[WHERE &amp;lt;条件表达式&amp;gt;]
[GROUP BY &amp;lt;列名&amp;gt;]
[HAVING &amp;lt;条件表达式&amp;gt;]
[ORDER BY &amp;lt;列名&amp;gt; [ASC | DESC] ];
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;单表查询&lt;/h4&gt;
&lt;p&gt;最简单的SQL查询只涉及一个关系（基本表），可归纳为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;选择表中的若干列（关系代数中的投影运算）。&lt;/li&gt;
&lt;li&gt;选择表中的若干元组（关系代数中的选择运算）。&lt;/li&gt;
&lt;li&gt;对查询进行分组。&lt;/li&gt;
&lt;li&gt;使用集函数。（&lt;code&gt;WHERE&lt;/code&gt;​ 作用于元组，&lt;code&gt;HAVING&lt;/code&gt;​ 作用于分组）&lt;/li&gt;
&lt;li&gt;对查询结果排序。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;连接查询&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;等值与非等值连接查询 (Equi-join and Non-equi-join Query)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用来连接两个表的条件称为连接条件或连接谓词，其一般格式为：&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;[&amp;lt;表名1&amp;gt;.]&amp;lt;列名1&amp;gt;&amp;lt;比较运算符&amp;gt;[&amp;lt;表名2&amp;gt;.]&amp;lt;列名2&amp;gt;&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;其中,比较运算符主要有：=,&amp;gt;,&amp;lt;,&amp;gt;=,&amp;lt;=、!=。&lt;/p&gt;
&lt;p&gt;此外,连接谓词还可以使用下面的形式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[&amp;lt;表名1&amp;gt;.]&amp;lt;列名1&amp;gt; BETWEEN [&amp;lt;表名2&amp;gt;.]&amp;lt;列名2&amp;gt; AND [&amp;lt;表名2&amp;gt;.]&amp;lt;列名3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当连接运算符为=时,称为等值连接,使用其他运算符时称为非等值连接。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;连接示例 (Join Example)：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT 学生.*, 学习.*
FROM 学生, 学习
WHERE 学生.学号 = 学习.学号;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;自然连接示例 (Natural Join Example)：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT 学生.学号, 姓名, 性别, 出生年份, 学院, 课程号, 成绩
FROM 学生, 学习
WHERE 学生.学号 = 学习.学号;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者，使用 &lt;code&gt;NATURAL JOIN&lt;/code&gt;​ 关键字：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT *
FROM 学生
NATURAL JOIN 学习;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;自身连接 (Self Join)：&lt;/strong&gt;  &lt;code&gt;同一关系与自身进行连接&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;外连接 (Outer Join)：&lt;/strong&gt;  &lt;code&gt;保留单边或双边未匹配元组的连接&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;复合条件连接 (Complex Condition Join)：&lt;/strong&gt;  &lt;code&gt;使用AND、OR或多个JOIN子句的复杂连接&lt;/code&gt;​&lt;/p&gt;
&lt;h4&gt;集合运算连接查询&lt;/h4&gt;
&lt;p&gt;使用集合运算（并、交、除）来组合关系。SQL为此提供了相应的运算符：&lt;code&gt;UNION&lt;/code&gt;​、&lt;code&gt;INTERSECT&lt;/code&gt;​、&lt;code&gt;EXCEPT&lt;/code&gt;​。&lt;/p&gt;
&lt;h4&gt;嵌套查询&lt;/h4&gt;
&lt;h5&gt;带有IN的子查询&lt;/h5&gt;
&lt;p&gt;判断属性列值是否在子查询结果（集合）中。&lt;/p&gt;
&lt;h5&gt;带有比较运算符的子查询&lt;/h5&gt;
&lt;p&gt;子查询结果为单值时，可直接使用比较运算符。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  查询选修了 &apos;数据库原理&apos; 课程的学生的学号和姓名。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT 学号, 姓名
FROM 学生
WHERE 学号 IN (
    SELECT 学号
    FROM 学习
    WHERE 课程号 = (
        SELECT 课程号
        FROM 课程
        WHERE 课程名 = &apos;数据库原理&apos;
    )
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;带有ANY/ALL的子查询&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;当子查询可能返回多行结果时，不能直接用比较运算符。此时用 &lt;code&gt;ANY&lt;/code&gt;​ 或 &lt;code&gt;ALL&lt;/code&gt;​ 谓词搭配比较运算符。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;strong&gt;​&lt;code&gt;ANY&lt;/code&gt;​&lt;/strong&gt;​：子查询结果中，只要有 &lt;em&gt;&lt;strong&gt;任意一个值&lt;/strong&gt;&lt;/em&gt; 满足比较关系，条件就成立。&lt;/li&gt;
&lt;li&gt;​&lt;strong&gt;​&lt;code&gt;ALL&lt;/code&gt;​&lt;/strong&gt;​：子查询结果中，&lt;em&gt;&lt;strong&gt;所有值&lt;/strong&gt;&lt;/em&gt; 都必须满足比较关系，条件才成立。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  查询比计算机学院任意一个学生年龄小的学生姓名。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT 姓名
FROM 学生
WHERE year(now()) - 出生年份 &amp;lt; ANY (
    SELECT year(now()) - 出生年份
    FROM 学生
    WHERE 学院 = &apos;计算机&apos;
)
ORDER BY year(now()) - 出生年份 DESC;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;等价于使用 MAX 聚合函数：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT 姓名
FROM 学生
WHERE year(now()) - 出生年份 &amp;lt; (
    SELECT MAX(year(now()) - 出生年份)
    FROM 学生
    WHERE 学院 = &apos;计算机&apos;
)
ORDER BY year(now()) - 出生年份 DESC;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;|&lt;strong&gt;ANY、ALL 谓词与集合函数及 IN 的等价转换关系：&lt;/strong&gt;|||||||&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;谓词&lt;/th&gt;
&lt;th&gt;=&lt;/th&gt;
&lt;th&gt;&amp;lt;&amp;gt; 或 !=&lt;/th&gt;
&lt;th&gt;&amp;lt;&lt;/th&gt;
&lt;th&gt;&amp;lt;=&lt;/th&gt;
&lt;th&gt;&amp;gt;&lt;/th&gt;
&lt;th&gt;&amp;gt;=&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ANY&lt;/td&gt;
&lt;td&gt;IN&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;&amp;lt; MAX&lt;/td&gt;
&lt;td&gt;&amp;lt;=MAX&lt;/td&gt;
&lt;td&gt;&amp;gt; MIN&lt;/td&gt;
&lt;td&gt;&amp;gt;=MIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALL&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;NOT IN&lt;/td&gt;
&lt;td&gt;&amp;lt; MIN&lt;/td&gt;
&lt;td&gt;&amp;lt;=MIN&lt;/td&gt;
&lt;td&gt;&amp;gt; MAX&lt;/td&gt;
&lt;td&gt;&amp;gt;=MAX&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;带有EXISTS的子查询&lt;/h5&gt;
&lt;p&gt;子查询不返回数据，只产生逻辑真值 &lt;code&gt;true&lt;/code&gt;​ 或 &lt;code&gt;false&lt;/code&gt;​。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  查询选修了 &apos;180102&apos; 号课程的学生的学号和姓名。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT 学号, 姓名
FROM 学生
WHERE EXISTS (
    SELECT *
    FROM 学习
    WHERE 学生.学号 = 学习.学号 AND 学习.课程号 = &apos;180102&apos;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;执行过程：&lt;/strong&gt;  依次取 &lt;code&gt;学生&lt;/code&gt;​ 表中元组，用其 &lt;code&gt;学号&lt;/code&gt;​ 检查 &lt;code&gt;学习&lt;/code&gt;​ 表。若 &lt;code&gt;学习&lt;/code&gt;​ 表中存在匹配 &lt;code&gt;学号&lt;/code&gt;​ 且 &lt;code&gt;课程号&lt;/code&gt;​ 为 &apos;180102&apos; 的记录，则 &lt;code&gt;EXISTS&lt;/code&gt;​ 返回 &lt;code&gt;true&lt;/code&gt;​，该学生记录的 &lt;code&gt;学号&lt;/code&gt;​ 和 &lt;code&gt;姓名&lt;/code&gt;​ 被选中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;​&lt;code&gt;NOT EXISTS&lt;/code&gt;​ 则相反。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;带 &lt;code&gt;EXISTS&lt;/code&gt;​/&lt;code&gt;NOT EXISTS&lt;/code&gt;​ 的子查询&lt;strong&gt;不一定&lt;/strong&gt;能被其他形式替代，但所有带 &lt;code&gt;IN&lt;/code&gt;​、比较运算符、&lt;code&gt;ANY&lt;/code&gt;​/&lt;code&gt;ALL&lt;/code&gt;​ 的子查询都能用 &lt;code&gt;EXISTS&lt;/code&gt;​ 等价替换。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;使用 EXISTS 实现除法&lt;/h5&gt;
&lt;p&gt;关系代数中的除法运算（查询选修了 &lt;em&gt;所有&lt;/em&gt; 某类课程的学生）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;核心思想：&lt;/strong&gt;  找到 &lt;em&gt;不存在&lt;/em&gt; 这样的课程：目标学生（如 &apos;091501&apos;）选修了，而当前被检查的学生 &lt;em&gt;没有&lt;/em&gt; 选修。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  查询选修了学号为 &apos;091501&apos; 的学生所选修的 &lt;em&gt;全部&lt;/em&gt; 课程的学生学号。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT DISTINCT 学号
FROM 选课表 AS S1
WHERE NOT EXISTS (
    SELECT 课程号
    FROM 选课表 AS S2
    WHERE S2.学号 = &apos;091501&apos;
      AND NOT EXISTS (
          SELECT 课程号
          FROM 选课表 AS S3
          WHERE S3.学号 = S1.学号
            AND S3.课程号 = S2.课程号
      )
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;精简解释：&lt;/strong&gt;  对于每个学生 &lt;code&gt;S1.学号&lt;/code&gt;​，检查是否 &lt;em&gt;不存在&lt;/em&gt; 课程 &lt;code&gt;S2.课程号&lt;/code&gt;​，满足条件： 课程 &lt;code&gt;S2.课程号&lt;/code&gt;​ 是学生 &apos;091501&apos; 选修的，并且学生 &lt;code&gt;S1.学号&lt;/code&gt;​ &lt;em&gt;没有&lt;/em&gt; 选修课程 &lt;code&gt;S2.课程号&lt;/code&gt;​。 如果不存在这样的课程，则说明学生 &lt;code&gt;S1.学号&lt;/code&gt;​ 选修了 &apos;091501&apos; 学生选修的所有课程。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;其他实现除法的方法：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用&lt;/strong&gt; &lt;strong&gt;​&lt;code&gt;GROUP BY&lt;/code&gt;​&lt;/strong&gt;​ &lt;strong&gt;和&lt;/strong&gt; &lt;strong&gt;​&lt;code&gt;HAVING&lt;/code&gt;​&lt;/strong&gt;​&lt;br /&gt;
统计每个学生选修的目标课程数，与目标学生选修的总课程数比较。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT 学号
FROM 选课表
WHERE 课程号 IN (SELECT 课程号 FROM 选课表 WHERE 学号 = &apos;091501&apos;)
GROUP BY 学号
HAVING COUNT(DISTINCT 课程号) = (SELECT COUNT(DISTINCT 课程号) FROM 选课表 WHERE 学号 = &apos;091501&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;​&lt;code&gt;对于相关的嵌套查询（如使用 EXISTS 且子查询引用了外层查询的列），其执行过程通常是：外层查询取出一个元组，将该元组的相关值传入内层子查询，内层子查询执行并返回结果（或真/假），外层查询根据内层结果判断当前元组是否满足条件，然后外层查询处理下一个元组。对于不相关的嵌套查询（子查询可以独立执行），通常是先执行内层子查询，得到结果集，然后外层查询使用这个结果集进行判断。&lt;/code&gt;​&lt;/p&gt;
&lt;h3&gt;数据更新&lt;/h3&gt;
&lt;h4&gt;插入数据 (INSERT)&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;插入单个元组 (行)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基本语法:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO &amp;lt;表名&amp;gt; [(&amp;lt;列名1&amp;gt;, &amp;lt;列名2&amp;gt;, ...)] VALUES (&amp;lt;值1&amp;gt;, &amp;lt;值2&amp;gt;, ...);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果省略列名列表，&lt;code&gt;VALUES&lt;/code&gt;​ 子句必须为所有列提供值，且顺序与表定义一致。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果指定列名列表，&lt;code&gt;VALUES&lt;/code&gt;​ 子句只需为指定的列提供值，顺序需对应，未指定的列将取默认值或 &lt;code&gt;NULL&lt;/code&gt;​。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- 插入完整信息
INSERT INTO t_employee (name, position, salary) VALUES (&apos;犬小哈&apos;, &apos;项目经理&apos;, 7000.00);
-- 插入部分列信息 (salary 会是 NULL 或默认值)
INSERT INTO t_employee (name, position) VALUES (&apos;李四&apos;, &apos;DBA 数据库工程师&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：命令行工具可能对中文支持不佳，建议使用图形客户端（如 Navicat）测试含中文的插入。&lt;/p&gt;
&lt;p&gt;MySQL 插入单行数据&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;插入多行数据&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一条 &lt;code&gt;INSERT&lt;/code&gt;​ 语句插入多个元组。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO t_employee (name, position, salary) VALUES
    (&apos;犬二哈&apos;, &apos;Java实习生&apos;, 3000.00),
    (&apos;犬大哈&apos;, &apos;Java高级工程师&apos;, 6000.00),
    (&apos;张三&apos;, &apos;运维工程师&apos;, 5000.00);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;!MySQL 查看数据&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;插入子查询结果&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;将查询结果插入表中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语法:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO &amp;lt;表名&amp;gt; [(&amp;lt;列名1&amp;gt;, &amp;lt;列名2&amp;gt;, ...)] &amp;lt;子查询&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  从 &lt;code&gt;t_employee_archive&lt;/code&gt;​ 表复制薪资高于 4000 的记录到 &lt;code&gt;t_employee&lt;/code&gt;​ 表。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO t_employee (name, position, salary)
SELECT name, position, salary FROM t_employee_archive WHERE salary &amp;gt; 4000;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;插入或替换 (REPLACE INTO)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果插入的行导致主键或唯一键冲突，则先删除旧行，再插入新行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REPLACE INTO t_employee (employee_id, name, position, salary) VALUES (1, &apos;王五&apos;, &apos;前端工程师&apos;, 6000.00);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;插入或更新 (ON DUPLICATE KEY UPDATE)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果插入的行导致主键或唯一键冲突，则执行 &lt;code&gt;UPDATE&lt;/code&gt;​ 子句指定的操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  尝试插入 ID 为 1 的记录，若已存在，则更新其职位和薪资。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO t_employee (employee_id, name, position, salary) VALUES (1, &apos;诸葛亮&apos;, &apos;安卓工程师&apos;, 7000.00)
ON DUPLICATE KEY UPDATE position = &apos;Java架构师&apos;, salary = 60000.00;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;注意事项:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;依赖主键或唯一键来检测冲突。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;​&lt;code&gt;UPDATE&lt;/code&gt;​ 子句可使用 &lt;code&gt;VALUES(column_name)&lt;/code&gt;​ 引用 &lt;code&gt;INSERT&lt;/code&gt;​ 语句中尝试插入的值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- 冲突时，用本次尝试插入的值更新记录
INSERT INTO t_employee (employee_id, name, position, salary) VALUES (1, &apos;诸葛亮&apos;, &apos;安卓工程师&apos;, 7000.00)
ON DUPLICATE KEY UPDATE name = VALUES(name), position = VALUES(position), salary = VALUES(salary);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;删除数据 (DELETE)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语法:&lt;/strong&gt;  &lt;code&gt;DELETE FROM &amp;lt;表名&amp;gt; [WHERE &amp;lt;条件&amp;gt;];&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;删除满足条件的元组:&lt;/strong&gt;  使用 &lt;code&gt;WHERE&lt;/code&gt;​ 子句。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  删除学号为 &apos;092010&apos; 的学生记录。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DELETE FROM 学生 WHERE 学号 = &apos;092010&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例（带子查询）：&lt;/strong&gt;  删除计算机学院所有学生的选课记录。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DELETE FROM 学习
WHERE &apos;计算机&apos; = (SELECT 学院 FROM 学生 WHERE 学生.学号 = 学习.学号);
-- 也可用 EXISTS 实现
DELETE FROM 学习
WHERE EXISTS (SELECT 1 FROM 学生 WHERE 学生.学号 = 学习.学号 AND 学院 = &apos;计算机&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;删除所有元组:&lt;/strong&gt;  省略 &lt;code&gt;WHERE&lt;/code&gt;​ 子句（慎用！）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DELETE FROM &amp;lt;表名&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;修改数据 (UPDATE)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语法:&lt;/strong&gt;  &lt;code&gt;UPDATE &amp;lt;表名&amp;gt; SET &amp;lt;列名&amp;gt;=&amp;lt;表达式&amp;gt; [, &amp;lt;列名&amp;gt;=&amp;lt;表达式&amp;gt;]... [WHERE &amp;lt;条件&amp;gt;];&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改满足条件的元组:&lt;/strong&gt;  使用 &lt;code&gt;WHERE&lt;/code&gt;​ 子句。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  将学号 &apos;091611&apos; 的学生籍贯改为 &apos;江苏&apos;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE 学生 SET 籍贯 = &apos;江苏&apos; WHERE 学号 = &apos;091611&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  将课程号 &apos;180101&apos; 的成绩加 1 分。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE 学习 SET 成绩 = 成绩 + 1 WHERE 课程号 = &apos;180101&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例（带子查询）：&lt;/strong&gt;  将计算机学院学生的成绩清零。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE 学习 SET 成绩 = 0
WHERE &apos;计算机&apos; = (SELECT 学院 FROM 学生 WHERE 学生.学号 = 学习.学号);
-- 也可用 EXISTS 实现
UPDATE 学习 SET 成绩 = 0
WHERE EXISTS (SELECT 1 FROM 学生 WHERE 学生.学号 = 学习.学号 AND 学院 = &apos;计算机&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改所有元组:&lt;/strong&gt;  省略 &lt;code&gt;WHERE&lt;/code&gt;​ 子句。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用子查询/连接更新:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  将员工薪资更新为其部门平均薪资（假设有 &lt;code&gt;t_department&lt;/code&gt;​ 表）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE t_employee AS e
JOIN t_department AS d ON e.department_id = d.department_id
SET e.salary = d.avg_salary;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用&lt;/strong&gt; &lt;strong&gt;​&lt;code&gt;ORDER BY&lt;/code&gt;​&lt;/strong&gt;​ &lt;strong&gt;和&lt;/strong&gt; &lt;strong&gt;​&lt;code&gt;LIMIT&lt;/code&gt;​&lt;/strong&gt;​ &lt;strong&gt;更新特定行:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  将薪资最低的 3 名员工薪资更新为 4500。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE t_employee
SET salary = 4500
ORDER BY salary ASC
LIMIT 3;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;视图 (Views)&lt;/h3&gt;
&lt;h4&gt;建立视图 (CREATE VIEW)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;视图是&lt;strong&gt;虚表&lt;/strong&gt;，基于基本表或其他视图，其定义存储在数据字典中，不存储实际数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE VIEW &amp;lt;视图名&amp;gt; [(&amp;lt;列名1&amp;gt; [,&amp;lt;列名2&amp;gt;]...)]
AS &amp;lt;子查询&amp;gt;
[WITH CHECK OPTION];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;列名列表：&lt;/strong&gt;  可省略，默认为子查询的 &lt;code&gt;SELECT&lt;/code&gt;​ 列。以下情况&lt;strong&gt;必须&lt;/strong&gt;指定：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;某列是聚合函数或表达式。&lt;/li&gt;
&lt;li&gt;多表连接时存在同名列。&lt;/li&gt;
&lt;li&gt;需要为列指定新名称。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;​&lt;strong&gt;​&lt;code&gt;WITH CHECK OPTION&lt;/code&gt;​&lt;/strong&gt;​ &lt;strong&gt;：&lt;/strong&gt;  对视图进行 &lt;code&gt;INSERT&lt;/code&gt;​ 或 &lt;code&gt;UPDATE&lt;/code&gt;​ 操作时，确保结果行满足视图定义中的 &lt;code&gt;WHERE&lt;/code&gt;​ 条件。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建计算机学院学生视图:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE VIEW CS_VIEW AS
SELECT * FROM 学生 WHERE 学院 = &apos;计算机&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建指定列名的视图:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE VIEW CS_VIEW (学号, 姓名, 性别, 籍贯, 出生年份, 学院) AS
SELECT 学号, 姓名, 性别, 籍贯, 出生年份, 学院 FROM 学生 WHERE 学院 = &apos;计算机&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基于多表创建视图 (选修数据库原理的计算机学院学生):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE VIEW DB_S1 AS
SELECT 学生.学号, 姓名, 性别, 籍贯, 学院, 成绩
FROM 学生, 学习, 课程
WHERE 课程名 = &apos;数据库原理&apos;
  AND 学生.学号 = 学习.学号
  AND 课程.课程号 = 学习.课程号
  AND 学院 = &apos;计算机&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基于视图创建视图 (成绩&amp;gt;=90):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE VIEW DB_S2 AS
SELECT * FROM DB_S1 WHERE 成绩 &amp;gt;= 90;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建带表达式的视图 (计算年龄):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE VIEW BT_S (学号, 姓名, 年龄) AS
SELECT 学号, 姓名, year(now()) - 出生年份 AS 年龄 FROM 学生;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;删除视图 (DROP VIEW)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;语法：&lt;/strong&gt;  &lt;code&gt;DROP VIEW &amp;lt;视图名&amp;gt;;&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;删除视图会使基于该视图导出的其他视图失效。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;查询视图&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;查询视图的操作与查询基本表类似。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;视图消解 (View Resolution):&lt;/strong&gt;  &lt;code&gt;视图消解是指数据库管理系统（DBMS）在执行对视图的查询时，会先将视图定义（即创建视图时的 AS 子查询）与用户的查询语句进行合并或重写，最终转换成对底层基本表的查询操作的过程。DBMS 并不实际存储视图的数据，而是存储其定义，查询时通过消解机制访问基础数据。&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;  查询计算机学院视图中年龄小于20岁的学生。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM CS_VIEW WHERE year(now()) - 出生年份 &amp;lt; 20;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;消解后的查询：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM 学生 WHERE 学院 = &apos;计算机&apos; AND year(now()) - 出生年份 &amp;lt; 20;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;更新视图&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;只有部分简单视图（通常称为&lt;strong&gt;可更新视图&lt;/strong&gt;，如行列子集视图）允许更新 (&lt;code&gt;INSERT&lt;/code&gt;​, &lt;code&gt;UPDATE&lt;/code&gt;​, &lt;code&gt;DELETE&lt;/code&gt;​)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;不可更新视图的常见限制：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;视图的列来自表达式或常数（通常不允许 &lt;code&gt;INSERT&lt;/code&gt;​/&lt;code&gt;UPDATE&lt;/code&gt;​，可能允许 &lt;code&gt;DELETE&lt;/code&gt;​）。&lt;/li&gt;
&lt;li&gt;视图的列来自聚合函数。&lt;/li&gt;
&lt;li&gt;视图定义中包含 &lt;code&gt;GROUP BY&lt;/code&gt;​ 子句。&lt;/li&gt;
&lt;li&gt;视图定义中包含 &lt;code&gt;DISTINCT&lt;/code&gt;​ 关键字。&lt;/li&gt;
&lt;li&gt;视图定义中包含嵌套查询，且嵌套查询的 &lt;code&gt;FROM&lt;/code&gt;​ 子句涉及导出该视图的基本表。&lt;/li&gt;
&lt;li&gt;视图基于两个或以上基本表导出（某些数据库可能有限支持）。&lt;/li&gt;
&lt;li&gt;视图基于不可更新的视图定义。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例（不可更新视图）：&lt;/strong&gt;  查询成绩高于平均分的选课记录视图。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE VIEW GOOD_SC_VIEW AS
SELECT 学号, 课程号, 成绩
FROM 学习
WHERE 成绩 &amp;gt; (SELECT AVG(成绩) FROM 学习);
-- 此视图通常不可更新，因为它包含子查询引用了自身的基础表
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;数据控制 (Data Control)&lt;/h3&gt;
&lt;h4&gt;授权 (GRANT)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;授予用户对数据库对象的操作权限。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GRANT &amp;lt;权限&amp;gt; [,&amp;lt;权限&amp;gt;]...
[ON &amp;lt;对象类型&amp;gt; &amp;lt;对象名&amp;gt;]
TO &amp;lt;用户&amp;gt; [,&amp;lt;用户&amp;gt;]...
[WITH GRANT OPTION];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;&amp;lt;权限&amp;gt;&lt;/code&gt;​: 如 &lt;code&gt;SELECT&lt;/code&gt;​, &lt;code&gt;INSERT&lt;/code&gt;​, &lt;code&gt;UPDATE&lt;/code&gt;​, &lt;code&gt;DELETE&lt;/code&gt;​, &lt;code&gt;ALL PRIVILEGES&lt;/code&gt;​ 等。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;&amp;lt;对象类型&amp;gt;&lt;/code&gt;​: 如 &lt;code&gt;TABLE&lt;/code&gt;​, &lt;code&gt;VIEW&lt;/code&gt;​ 等。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;&amp;lt;对象名&amp;gt;&lt;/code&gt;​: 表名、视图名等。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;&amp;lt;用户&amp;gt;&lt;/code&gt;​: 用户名或 &lt;code&gt;PUBLIC&lt;/code&gt;​ (所有用户)。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;WITH GRANT OPTION&lt;/code&gt;​: 允许被授权用户将获得的权限再授予其他用户。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;常用权限:&lt;/strong&gt;  (详见教材或文档，如 表 3-39)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;授予 &lt;code&gt;User1&lt;/code&gt;​ 对 &lt;code&gt;学生&lt;/code&gt;​ 表的查询权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GRANT SELECT ON TABLE 学生 TO User1;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;授予 &lt;code&gt;User2&lt;/code&gt;​, &lt;code&gt;User3&lt;/code&gt;​ 对 &lt;code&gt;学生&lt;/code&gt;​ 表和 &lt;code&gt;课程&lt;/code&gt;​ 表的所有权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GRANT ALL PRIVILEGES ON TABLE 学生, 课程 TO User2, User3;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;授予所有用户对 &lt;code&gt;学习&lt;/code&gt;​ 表的查询权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GRANT SELECT ON TABLE 学习 TO PUBLIC;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;授予 &lt;code&gt;User4&lt;/code&gt;​ 对 &lt;code&gt;学习&lt;/code&gt;​ 表的查询权限和修改 &lt;code&gt;成绩&lt;/code&gt;​ 列的权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GRANT UPDATE (成绩), SELECT ON TABLE 学习 TO User4;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;授予 &lt;code&gt;User5&lt;/code&gt;​ 对 &lt;code&gt;学生&lt;/code&gt;​ 表的 &lt;code&gt;INSERT&lt;/code&gt;​ 权限，并允许其传播此权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GRANT INSERT ON TABLE 学生 TO User5 WITH GRANT OPTION;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;收回权限 (REVOKE)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;收回已授予用户的权限。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语法：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REVOKE &amp;lt;权限&amp;gt; [,&amp;lt;权限&amp;gt;]...
[ON &amp;lt;对象类型&amp;gt; &amp;lt;对象名&amp;gt;]
FROM &amp;lt;用户&amp;gt; [,&amp;lt;用户&amp;gt;]...;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;收回 &lt;code&gt;User4&lt;/code&gt;​ 修改 &lt;code&gt;学习&lt;/code&gt;​ 表 &lt;code&gt;成绩&lt;/code&gt;​ 的权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REVOKE UPDATE (成绩) ON TABLE 学习 FROM User4;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;收回 &lt;code&gt;User5&lt;/code&gt;​ 对 &lt;code&gt;学生&lt;/code&gt;​ 表的 &lt;code&gt;INSERT&lt;/code&gt;​ 权限（级联收回）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REVOKE INSERT ON TABLE 学生 FROM User5;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;User5&lt;/code&gt;​ 曾将此 &lt;code&gt;INSERT&lt;/code&gt;​ 权限授予 &lt;code&gt;User6&lt;/code&gt;​，则 &lt;code&gt;User6&lt;/code&gt;​ 从 &lt;code&gt;User5&lt;/code&gt;​ 处获得的该权限也会被收回。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;关系规范化理论（待续）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;函数依赖 (Functional Dependency):&lt;/strong&gt;  Y 依赖于 X，或 X 函数确定 Y，记为 &lt;code&gt;X -&amp;gt; Y&lt;/code&gt;​。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SQL 连接类型补充:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;INNER JOIN&lt;/code&gt;​ / &lt;code&gt;JOIN&lt;/code&gt;​: 只保留两个表中能匹配上的行（交集）。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;LEFT JOIN&lt;/code&gt;​ (或 &lt;code&gt;LEFT OUTER JOIN&lt;/code&gt;​): 保留左表所有行，右表只保留匹配行，无匹配则为 &lt;code&gt;NULL&lt;/code&gt;​。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;RIGHT JOIN&lt;/code&gt;​ (或 &lt;code&gt;RIGHT OUTER JOIN&lt;/code&gt;​): 保留右表所有行，左表只保留匹配行，无匹配则为 &lt;code&gt;NULL&lt;/code&gt;​。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;FULL OUTER JOIN&lt;/code&gt;​: 保留左右两表所有行，无匹配则对应表列为 &lt;code&gt;NULL&lt;/code&gt;​ (MySQL 不直接支持，需用 &lt;code&gt;LEFT JOIN UNION RIGHT JOIN&lt;/code&gt;​ 模拟)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;范式 (Normal Forms):&lt;/strong&gt;
​&lt;img src=&quot;./assets/sjk12.jpg&quot; alt=&quot;SmartSelect_20250331_080415_Moon+ Reader Pro&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1NF (第一范式):&lt;/strong&gt;  关系中的所有属性都是原子的，不可再分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2NF (第二范式):&lt;/strong&gt;  满足 1NF，且每一个非主属性都 &lt;em&gt;完全函数依赖&lt;/em&gt; 于候选码（不能只依赖于候选码的一部分）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3NF (第三范式):&lt;/strong&gt;  满足 2NF，且消除非主属性对候选码的 &lt;em&gt;传递函数依赖&lt;/em&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BCNF (Boyce-Codd 范式):&lt;/strong&gt;  满足 3NF，且所有函数依赖中，决定因素（&lt;code&gt;-&amp;gt;&lt;/code&gt;​左侧）都必须包含候选码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多值依赖和 4NF (第四范式):&lt;/strong&gt;  ?[需要人工补充]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;相关理论:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据依赖的公理系统 (Armstrong 公理)&lt;/li&gt;
&lt;li&gt;函数依赖集的闭包 (F+)&lt;/li&gt;
&lt;li&gt;函数依赖的推理规则&lt;/li&gt;
&lt;li&gt;属性集闭包 (X+)&lt;/li&gt;
&lt;li&gt;F 逻辑蕴含 G 的充要条件&lt;/li&gt;
&lt;li&gt;码的理论 (候选码、主码、超码)&lt;/li&gt;
&lt;li&gt;函数依赖集的等价和最小函数依赖集 (Minimal Cover)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;‍&lt;/p&gt;
</content:encoded></item><item><title>Gemini 2.5 音视频识别实测，震撼</title><link>https://duke486.com/posts/gemini-25-%E9%9F%B3%E8%A7%86%E9%A2%91%E8%AF%86%E5%88%AB%E8%83%BD%E5%8A%9B%E5%AE%9E%E6%B5%8B%E9%9C%87%E6%92%BC%E5%88%B0%E6%88%91%E4%BA%86/%E8%B0%B7%E6%AD%8Cgemini%E9%9F%B3%E8%A7%86%E9%A2%91%E5%AE%9E%E6%B5%8B%E4%BB%A5%E5%90%8E%E5%86%8D%E4%B9%9F%E4%B8%8D%E9%9C%80%E8%A6%81%E5%AD%97%E5%B9%95%E7%BB%84%E4%BA%86/</link><guid isPermaLink="true">https://duke486.com/posts/gemini-25-%E9%9F%B3%E8%A7%86%E9%A2%91%E8%AF%86%E5%88%AB%E8%83%BD%E5%8A%9B%E5%AE%9E%E6%B5%8B%E9%9C%87%E6%92%BC%E5%88%B0%E6%88%91%E4%BA%86/%E8%B0%B7%E6%AD%8Cgemini%E9%9F%B3%E8%A7%86%E9%A2%91%E5%AE%9E%E6%B5%8B%E4%BB%A5%E5%90%8E%E5%86%8D%E4%B9%9F%E4%B8%8D%E9%9C%80%E8%A6%81%E5%AD%97%E5%B9%95%E7%BB%84%E4%BA%86/</guid><description>通过多个对Gemini 2.5 Pro的音视频测试案例，发现其音视频识别能力已达到了惊人的水平，甚至可以为普通动漫视频加字幕，且准确率极高。</description><pubDate>Sun, 30 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;谷歌Gemini音视频实测！以后再也不需要字幕组了？&lt;/p&gt;
&lt;h2&gt;Gemini 2.5 Pro 是啥&lt;/h2&gt;
&lt;p&gt;谷歌在前几天（25年3月25号）发布了 Gemini 2.5 Pro 模型，综合排名世界第一，号称有&lt;strong&gt;百万上下文&lt;/strong&gt;，后期将会开放 200 万上下文。最大输出长度约为 6 万 token。&lt;br /&gt;
刚发布时我就快速体验了一下代码和散文写作能力，毫无疑问就我个人体验而言，Gemini 2.5 Pro 是目前效果最强的。尤其是文章写作，剧情连贯，措辞合理，不像 DeepSeek R1、Claude 3.7 Thinking 等思考模型，会自我意识过剩导致文章中意象和辞藻堆砌。&lt;br /&gt;
&lt;strong&gt;不过本文的重点不在此&lt;/strong&gt;。今天我看到了网络上有人用 2.5Pro 处理一段多语言视频的效果展示，于是便想要测试一下 Gemini 对音视频的理解能力如何、能处理多长的音视频。谷歌在 AI Studio 网站给所有人提供免费使用，我立刻开始了测试。结果令我震撼，尤其是测试 4：动漫加字幕！&lt;/p&gt;
&lt;h2&gt;测试内容&lt;/h2&gt;
&lt;p&gt;测试主要关注这几个方面：音频内容识别、音视频时间轴、视频内容理解、音视频上下文连贯性理解、音效音乐等元素识别。&lt;br /&gt;
&lt;strong&gt;太长不看，结论&lt;/strong&gt;：除了中文歌和刁钻题材（专有词地狱的动画、发音人类都很难听懂的视频）之外，Gemini 无敌了，可以完美给视频加字幕！&lt;br /&gt;
我准备了&lt;strong&gt;下面几个任务&lt;/strong&gt;，依次来看看结果吧。以下素材在喂给 AI 之前均已经裁掉字幕，提供给 AI 信息的只有字幕要求以及来源作品名称，也就是说角色名字和额外剧情信息均为 Gemini 依靠知识库脑补的！&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;《你想活出怎样的人生》插曲视频片段——要求输出画面内容、剧情、中文字幕、日文字幕、音效描述&lt;/li&gt;
&lt;li&gt;《你的名字》英文版片头曲视频——要求输出画面内容、英文字幕、中文字幕、音效描述&lt;/li&gt;
&lt;li&gt;初音未来《Miku》短视频——要求输出画面内容、中日歌词&lt;/li&gt;
&lt;li&gt;大的来了，《New Game》第一集全部音频👍——要求输出中日字幕、音效、根据声线与上下文推断说话人名字、OP/ED 中日歌词&lt;/li&gt;
&lt;li&gt;中日英歌曲各一首，吐字较为清晰，《Hello World 》- Kizuna AI，《真昼の空の月》- アビドス高等学校対策委員会，《漂亮面对》- 洛天依; 阿良良木健——要求输出双语滚动歌词、每句话的歌唱感情&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;测试结果&lt;/h2&gt;
&lt;p&gt;所有结果我都人工检查过通篇，有错误会指出。如果没指出则代表&lt;strong&gt;通篇都正确&lt;/strong&gt;。&lt;br /&gt;
所有的时间轴均精确到 0.01 秒，可以和视频&lt;strong&gt;完美对应&lt;/strong&gt;，下文不再提及时间轴。&lt;/p&gt;
&lt;h3&gt;1《你想活出怎样的人生》&lt;/h3&gt;
&lt;p&gt;结论：完美完成任务。无任何错误。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20250330200924-5ae091o.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;画面内容识别↑&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;./assets/image-20250330201003-y08gh42.png&quot; alt=&quot;image&quot; /&gt;​&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330201129-zinij7h.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;↑知识库丰富度、剧情上下文识别。可以正确认识角色，可以识别安慰剧情。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;./assets/image-20250330201257-nwuv0jk.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;↑音效和音乐识别&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;./assets/image-20250330201445-dfhwio6.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;↑嘈杂环境中的语音识别&lt;/p&gt;
&lt;h3&gt;2 《你的名字》英文版片头&lt;/h3&gt;
&lt;p&gt;英语歌词部分出现个位数识别错误，为同音词或短语连读出错，基本没有影响汉语意思。其他方面完美完成任务，甚至可以根据电影剧情补充信息，例如“口嚼酒”、“东京街道”等。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330201826-1drkby4.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330201859-xljeb0v.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
↑画面识别，音效识别&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330201911-dw3uklv.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330201927-zyh8w19.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330201952-s2nonsq.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
↑根据电影剧情补充画面描述信息，例如组纽、系守、口嚼酒&lt;/p&gt;
&lt;h3&gt;3 《Miku》短视频&lt;/h3&gt;
&lt;p&gt;视频镜头快速切换的时候，字幕中漏掉了一两个分镜。歌词与音乐节奏识别完美。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330202246-5g4lt6q.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330202256-e6crnqi.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330202319-23lg60r.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4 《New Game》第一集👍&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;本测试只提供了音频和作品名称给 AI。&lt;/strong&gt;
极个别台词没分清说话人是谁，不过这个要求对于只听音频来说有点难了。&lt;br /&gt;
角色尖叫的时候，有概率识别不出在叫什么（有时候成功，有时候只显示尖叫）。&lt;br /&gt;
其他地方完美。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330202640-328azbj.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
↑语音识别与角色名推断，即使是初次登场也可以自动推断发言角色名。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330202735-4cfbmre.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330202839-vrjftnd.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
↑片假名专有名词自动补充英文、或者直接使用剧中原文（我没要求它）&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330202959-5w1x7dj.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330203014-7dyavxl.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330203112-bd6f32t.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
↑日语、英语混合 OPED 识别，非常准确。甚至能在角色发言的时候，同时识别台词和歌词（图 3）&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330203142-pd4j6c7.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
↑&lt;strong&gt;最逆天的&lt;/strong&gt;，角色默念的所有短信全都标注了【message】，有的短信甚至没有上下文可供推测，我怀疑它能识别角色内心独白时候的回音效果。&lt;/p&gt;
&lt;h3&gt;5 中日英三首歌&lt;/h3&gt;
&lt;p&gt;时间轴全对。日文错了两个单词，英文全对，中文错了接近一半。这里直接放结果图。&lt;br /&gt;
日文，只有开头错了两个词。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330203452-3hwi4qj.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
英文，全对。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330203501-6800qy6.png&quot; alt=&quot;image&quot; /&gt;&lt;br /&gt;
中文，错一半，不标注了。&lt;br /&gt;
​&lt;img src=&quot;./assets/image-20250330203510-u08es6j.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;能处理多长的视频&lt;/h2&gt;
&lt;p&gt;我没有测试极限长度。但是可以根据 token 使用量大致推算，我在上传视频时，为了方便 AI 处理，将视频码率压缩到了 2Mbps，一分钟视频大约占用 4-6 万 token。24 分钟音频占用 5 万 token。&lt;/p&gt;
&lt;p&gt;处理一集动漫长度的视频大约需要 2 万 token。由此可见，输入长度并非瓶颈，真正的瓶颈在于输出长度。如果以动漫视频的 token 消耗量计算，一次性最长可以处理 60-80 分钟的音频。如果是网课视频，处理时长可能会更短。&lt;/p&gt;
&lt;p&gt;不过总的来说，目前来看还是非常够用的，毕竟在现阶段，我们不太可能上传整部电影。Gemini 这一举措无疑为 AI 应用开辟了更广阔的道路！&lt;/p&gt;
&lt;h2&gt;结论&lt;/h2&gt;
&lt;p&gt;看来 AI 的进化超出了我的想象。我还停留在用一堆工作流+人工辅助打轴的认知呢，现在看，AI 来为日常番、外语网课加字幕，已经可以做到无敌了。甚至可以为视力障碍者直接描述剧情和画面。一切都发生得太快了。&lt;/p&gt;
</content:encoded></item><item><title>Go+Gin+Gorm 入门项目笔记</title><link>https://duke486.com/posts/gogingorm%E5%85%A5%E9%97%A8%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://duke486.com/posts/gogingorm%E5%85%A5%E9%97%A8%E9%A1%B9%E7%9B%AE%E7%AC%94%E8%AE%B0/</guid><description>跟着InkkaPlum的Go+Gin+Gorm开源教程，学习Go语言的Web开发和数据库操作。</description><pubDate>Tue, 25 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Go Gin Gorm&lt;/h2&gt;
&lt;h3&gt;精炼笔记：Go + Gin + Gorm 项目&lt;/h3&gt;
&lt;p&gt;这个教程内容涵盖了Go + Gin + Gorm + MySQL + Redis 的技术栈。下面是我学习后的精华笔记，按模块叙述了每个重要的知识点和代码实现。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1BY4UefEkM&quot;&gt;课程视频：InkkaPlum频道&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Slumhee/Web003Gin-01_gingormtutorials&quot;&gt;配套资料：Github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gin-gonic.com/zh-cn/docs/examples/param-in-path/&quot;&gt;Gin官方文档（模板）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gorm.io/zh_CN/docs/query.html&quot;&gt;GORM官方文档&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;1. 项目结构设计&lt;/h3&gt;
&lt;h4&gt;1.1 项目目录结构&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;./assets/image-20250311142620-e25pv8x.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;首先，在main.go开始运行时，config包负责读取程序的配置文件，并按照配置连接数据库，将数据库实例保存到global包的变量中方便调用。&lt;/p&gt;
&lt;p&gt;之后，调用router包进行路由初始化设置，初始化函数返回一个&lt;code&gt;r *gin.Engine&lt;/code&gt;​。main.go将在goroutine中使用&lt;code&gt;r&lt;/code&gt;​来非阻塞式地启动服务，监听配置文件中指定的端口。&lt;/p&gt;
&lt;p&gt;当接收到请求的时候，http包的上下文数据会按照路由规则交给不同的controllers和中间件处理。utils中定义了程序常用的工具、方法，models中定义了文章、用户等数据的结构、数据库的表结构。&lt;/p&gt;
&lt;p&gt;服务运行时，main将会被channel阻塞。当程序接收到的系统的停止信号时，将会开始进行优雅关闭，停止接受连接并释放资源。&lt;/p&gt;
&lt;h4&gt;1.2 资源和API设计&lt;/h4&gt;
&lt;p&gt;这套教程的目标是开发一个能够实现汇率查询、文章获取、点赞、用户注册、登录功能的，使用mySQL和Redis数据库的go后端项目。因此应该基于这个最终目标来设计各种API。&lt;/p&gt;
&lt;p&gt;RESTful API：一种设计风格&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RESTful API&lt;/strong&gt;：基于资源设计路径，使用 HTTP 方法定义操作。REST（Representational State Transfer，表述性状态转移）本身不是一种技术，而是一组设计约束。遵循这些约束设计的 API 就被称为 RESTful API。&lt;/p&gt;
&lt;p&gt;在同一个路径上，可以通过不同的HTTP方法和参数来区分不同的功能，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;GET /api/articles&lt;/code&gt;​ 获取所有文章&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;POST /api/articles&lt;/code&gt;​ 创建新文章&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;GET /api/articles/:id&lt;/code&gt;​ 获取单篇文章&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;POST /api/articles/:id/like&lt;/code&gt;​ 为文章点赞&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;请求路径设计如下&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;/api/v1/articles&lt;/code&gt;​：文章相关资源&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;/api/v1/auth/login&lt;/code&gt;​：登录接口&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;/api/v1/auth/register&lt;/code&gt;​：注册接口&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;1.3 使用HTTP状态码向客户端反馈&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;200 OK&lt;/code&gt;​：请求成功&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;201 Created&lt;/code&gt;​：成功创建资源&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;400 Bad Request&lt;/code&gt;​：请求无效，通常由参数错误引起&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;401 Unauthorized&lt;/code&gt;​：未授权的请求&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;404 Not Found&lt;/code&gt;​：请求的资源不存在&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;500 Internal Server Error&lt;/code&gt;​：服务器错误&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;2. Gin框架&lt;/h3&gt;
&lt;h4&gt;2.1 简单配置和初始化&lt;/h4&gt;
&lt;p&gt;Gin框架是Go语言中高效的Web框架，用于处理HTTP请求和响应。这是一个最小的gin服务，监听8080端口&lt;code&gt;/ping&lt;/code&gt;​路径的GET请求，并返回json。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import &quot;github.com/gin-gonic/gin&quot;

func main() {
    r := gin.Default()
    r.GET(&quot;/ping&quot;, func(c *gin.Context) {
        c.JSON(200, gin.H{&quot;message&quot;: &quot;pong&quot;})
    })
    r.Run(&quot;:8080&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;gin.Default()&lt;/code&gt;​ 创建带有日志和恢复中间件的默认引擎。&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;c.JSON()&lt;/code&gt;​ 用于返回JSON格式的响应。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.2 路由与分组&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;r := gin.Default()

// 路由分组
auth := r.Group(&quot;/api/auth&quot;)
{
    auth.POST(&quot;/login&quot;, controllers.Login)
    auth.POST(&quot;/register&quot;, controllers.Register)

}

api := r.Group(&quot;/api&quot;)
api.GET(&quot;/exchangeRates&quot;, controllers.GetExchangeRates)
api.Use(middlewares.AuthMiddleWare())
{
    //此处的api都将受到用户认证中间件的保护，登录用户才能使用
    api.POST(&quot;/exchangeRates&quot;, controllers.CreateExchangeRate)
    api.POST(&quot;/articles&quot;, controllers.CreateArticle)
    api.GET(&quot;/articles&quot;, controllers.GetArticles)
    api.GET(&quot;/articles/:id&quot;, controllers.GetArticlesByID)
    api.POST(&quot;/articles/:id/like&quot;, controllers.LikeArticle)
    api.GET(&quot;/articles/:id/like&quot;, controllers.GetArticleLikes)
}
)
    

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;Group&lt;/code&gt;​可以对相似的路由进行分组，有助于管理和维护。&lt;/li&gt;
&lt;li&gt;会路由指向不同路径的请求，并将请求上下文交给handler函数处理&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.3 中间件&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func AuthMiddleWare() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        token := ctx.GetHeader(&quot;Authorization&quot;)
        //获取请求头中的token
        if token == &quot;&quot; {
        //无token，结束处理
            ctx.JSON(http.StatusUnauthorized, gin.H{&quot;error&quot;: &quot;Token is missing&quot;})
            ctx.Abort()
            return
        }

        username, err := utils.ParseJWT(token)

        if err != nil {
            ctx.JSON(http.StatusUnauthorized, gin.H{&quot;error&quot;: &quot;Invalid token&quot;})
            ctx.Abort()
            return
        }
        ctx.Set(&quot;username&quot;, username)
        //解析成功，获得用户名，进行下一步操作
        ctx.Next()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;自定义中间件，用于记录请求时间、验证用户身份或执行其他操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.4 请求绑定&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;type Login struct {
    Username string `json:&quot;username&quot; binding:&quot;required&quot;`
    Password string `json:&quot;password&quot; binding:&quot;required&quot;`
}

func LoginHandler(c *gin.Context) {
    var login Login
    if err := c.ShouldBindJSON(&amp;amp;login); err != nil {
        c.JSON(400, gin.H{&quot;error&quot;: err.Error()})
        return
    }
    c.JSON(200, gin.H{&quot;message&quot;: &quot;Login successful&quot;})
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;ShouldBindJSON()&lt;/code&gt;​ 绑定JSON数据到结构体，并进行验证。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;3. Gorm ORM&lt;/h3&gt;
&lt;h4&gt;3.1 配置与数据库连接&lt;/h4&gt;
&lt;p&gt;Gorm是Go中常用的ORM库，用于简化与数据库的交互。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func initDB() {

    db, err := gorm.Open(mysql.Open(AppConfig.Database.Dsn), &amp;amp;gorm.Config{})

    if err != nil {
        log.Fatalf(&quot;failed to connect database: %v&quot;, err)
    }

    sqlDB, err := db.DB()
    //设置连接的限制
    sqlDB.SetMaxIdleConns(AppConfig.Database.MaxIdleConns)
    sqlDB.SetMaxOpenConns(AppConfig.Database.MaxOpenConns)
    sqlDB.SetConnMaxLifetime(time.Hour)

    if err != nil {
        log.Fatalf(&quot;failed to config database: %v&quot;, err)
    }
    // 添加这段代码，在应用启动时迁移所有模型
    if err := db.AutoMigrate(&amp;amp;models.User{}, &amp;amp;models.ExchangeRate{}); err != nil {
        log.Fatalf(&quot;failed to auto migrate: %v&quot;, err)
    }
    //存入global方便使用
    global.Db = db
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;mysql.Open(dsn)&lt;/code&gt;​：连接数据库&lt;/li&gt;
&lt;li&gt;​&lt;code&gt;gorm.Config{}&lt;/code&gt;​：提供数据库连接的配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.2 模型定义&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;package models

import &quot;gorm.io/gorm&quot;

type User struct {
    gorm.Model
    Username string `gorm:&quot;unique&quot;`
    Password string
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;​&lt;code&gt;gorm:&quot;primaryKey&quot;&lt;/code&gt;​：定义主键字段。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;​&lt;code&gt;gorm:&quot;uniqueIndex&quot;&lt;/code&gt;​：确保字段唯一。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;gorm.models是自带的，包含了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code&gt;type Model struct { // size=88 (0x58)
    ID        uint `gorm:&quot;primarykey&quot;`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt DeletedAt `gorm:&quot;index&quot;`
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.3 数据库操作&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 创建用户
db.Create(&amp;amp;User{Username: &quot;john&quot;, Password: &quot;12345&quot;})

// 查询用户
var user User
db.Where(&quot;username = ?&quot;, &quot;john&quot;).First(&amp;amp;user)

// 更新用户
db.Model(&amp;amp;user).Update(&quot;Password&quot;, &quot;newpassword&quot;)

// 删除用户
db.Delete(&amp;amp;user)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.4 自动迁移&lt;/h4&gt;
&lt;p&gt;​&lt;code&gt;AutoMigrate&lt;/code&gt;​方法可以自动同步数据库表结构。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;db.AutoMigrate(&amp;amp;models.User{})
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.5 数据库连接池配置&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;sqlDB, err := db.DB()
if err != nil {
    log.Fatal(err)
}
sqlDB.SetMaxIdleConns(10) // 设置最大空闲连接数
sqlDB.SetMaxOpenConns(100) // 设置最大连接数
sqlDB.SetConnMaxLifetime(time.Hour) // 设置最大连接生命周期
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;4. 配置与初始化&lt;/h3&gt;
&lt;h4&gt;4.1 配置文件（YAML）&lt;/h4&gt;
&lt;p&gt;为了方便修改程序的配置，避免把数据库地址、密码等信息写死在代码中，可以使用yaml来存储配置信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# config/config.yml
app:
  name: CurrencyExchangeApp
  port: &quot;:3000&quot;

database:
  host: &quot;localhost&quot;
  port: &quot;3306&quot;
  user: &quot;root&quot;
  password: &quot;password&quot;
  name: &quot;currency_exchange_db&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4.2 配置加载&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func InitConfig() {
    viper.SetConfigName(&quot;config&quot;)
    viper.SetConfigType(&quot;yml&quot;)
    viper.AddConfigPath(&quot;./config&quot;)

    if err := viper.ReadInConfig(); err != nil {
        log.Fatalf(&quot;读配置文件失败: %v&quot;, err)
    }

    AppConfig = &amp;amp;Config{}

    if err := viper.Unmarshal(AppConfig); err != nil {
        log.Fatalf(&quot;解析配置文件失败: %v&quot;, err)
    }

    initDB()
    initRedis()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;viper&lt;/code&gt;​加载YAML配置文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4.3 配置结构体&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;type Config struct {
    App struct {
        Name string
        Port string
    }
    Database struct {
        Dsn          string
        MaxIdleConns int
        MaxOpenConns int
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;viper.Unmarshal&lt;/code&gt;​将配置映射到结构体。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;5. 身份验证与JWT&lt;/h3&gt;
&lt;h4&gt;5.1 JWT生成与验证&lt;/h4&gt;
&lt;p&gt;JWT（JSON Web Token）用于实现用户身份验证。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func GenerateJWT(username string) (string, error) {
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        &quot;username&quot;: username,
        &quot;exp&quot;:      time.Now().Add(time.Hour * 24).Unix(),
    })
    //使用密钥签名
    SignedToken, err := token.SignedString([]byte(&quot;secret&quot;))
    return &quot;Bearer &quot; + SignedToken, err
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;​&lt;code&gt;jwt.NewWithClaims()&lt;/code&gt;​：根据指定的签名方法和声明生成JWT。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对应的，可以使用相反的流程来解析JWT：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func ParseJWT(tokenString string) (string, error) {
    // 移除 &quot;Bearer &quot; 前缀
    if len(tokenString) &amp;gt; 7 &amp;amp;&amp;amp; tokenString[:7] == &quot;Bearer &quot; {
        tokenString = tokenString[7:]
    }

    // 解析 JWT token
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // 检查 signing 方法
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, errors.New(&quot;unexpected signing method&quot;) // signing 方法错误
        }
        return []byte(&quot;secret&quot;), nil // 秘钥
    })

    if err != nil {
        return &quot;&quot;, err // 解析失败
    }

    // 验证 token 并提取 claims
    if claims, ok := token.Claims.(jwt.MapClaims); ok &amp;amp;&amp;amp; token.Valid {
        username, ok := claims[&quot;username&quot;].(string)
        if !ok {
            return &quot;&quot;, errors.New(&quot;Username not found in token&quot;) // username 不存在
        }
        return username, nil // 成功返回 username
    }

    return &quot;&quot;, errors.New(&quot;Invalid token&quot;) // 无效 token
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;6. 用户注册与登录&lt;/h3&gt;
&lt;h4&gt;6.1 用户注册&lt;/h4&gt;
&lt;p&gt;​&lt;code&gt;auth_controller.go&lt;/code&gt;​：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func Register(ctx *gin.Context) {
    var user models.User
    //请求json绑定到结构体
    if err := ctx.ShouldBindJSON(&amp;amp;user); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return
    }
    //两次调用utils中的函数
    hashedPwd, err := utils.HashPassword(user.Password)

    if err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
    }
    user.Password = hashedPwd

    token, err := utils.GenerateJWT(user.Username)

    if err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return
    }
    //尝试在数据库中创建用户
    if err := global.Db.Create(&amp;amp;user).Error; err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return
    }
    //没有出错，返回新用户的token
    ctx.JSON(http.StatusOK, gin.H{
        &quot;token&quot;: token,
    })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;用户密码加密存储，生成JWT并返回。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;6.2 用户登录&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func Login(ctx *gin.Context) {
    // 定义输入结构体
    var input struct {
        Username string `json:&quot;username&quot;`
        Password string `json:&quot;password&quot;`
    }

    // 绑定JSON数据到结构体
    if err := ctx.ShouldBindJSON(&amp;amp;input); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{&quot;error&quot;: err.Error()})
        return
    }

    var user models.User

    // 根据用户名查询用户
    if err := global.Db.Where(&quot;username = ?&quot;, input.Username).First(&amp;amp;user).Error; err != nil {
        ctx.JSON(http.StatusUnauthorized, gin.H{&quot;error&quot;: &quot;invalid username or password&quot;})
        return
    }

    // 验证密码
    if !utils.CheckPassword(input.Password, user.Password) {
        ctx.JSON(http.StatusUnauthorized, gin.H{&quot;error&quot;: &quot;invalid username or password&quot;})
        return
    }

    // 生成JWT token
    token, err := utils.GenerateJWT(user.Username)
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{&quot;error&quot;: err.Error()})
        return
    }

    // 返回token
    ctx.JSON(http.StatusOK, gin.H{&quot;token&quot;: token})
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;7. 点赞缓存&lt;/h3&gt;
&lt;h4&gt;7.1 Redis缓存（文章点赞）&lt;/h4&gt;
&lt;p&gt;初始化Redis：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func initRedis() {
    redisClient := redis.NewClient(&amp;amp;redis.Options{
        Addr:     &quot;localhost:6379&quot;,
        DB:       0,
        Password: &quot;&quot;,
    })

    _, err := redisClient.Ping().Result()
    if err != nil {
        log.Fatalf(&quot;Failed to connect redis: %v&quot;, err)
    }

    global.RedisDB = redisClient
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用Go-Redis库与Redis服务器交互。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;7.2 文章点赞&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func LikeArticle(ctx *gin.Context) {
    articleID := ctx.Param(&quot;id&quot;)
    //组装出键
    likeKey := &quot;article:&quot; + articleID + &quot;:likes&quot;
    if err := global.RedisDB.Incr(likeKey).Err(); err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return
    }

    ctx.JSON(http.StatusOK, gin.H{
        &quot;message&quot;: &quot;点赞成功&quot;,
    })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;Incr&lt;/code&gt;​命令递增文章的点赞数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;7.3 获取点赞&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func GetArticleLikes(ctx *gin.Context) {
    articleID := ctx.Param(&quot;id&quot;)
    fmt.Print(articleID)
    likeKey := &quot;article:&quot; + articleID + &quot;:likes&quot;

    likes, err := global.RedisDB.Get(likeKey).Result()
    if err == redis.Nil {
        likes = &quot;0&quot;//没有数据则创建一个0值
    } else if err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
    }

    ctx.JSON(http.StatusOK, gin.H{
        &quot;likes&quot;: likes,
    })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8. 文章与缓存&lt;/h3&gt;
&lt;h4&gt;8.1 创建文章并更新缓存&lt;/h4&gt;
&lt;p&gt;在创建文章的同时，删除Articles缓存，避免用户获取到旧的文章列表。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func CreateArticle(ctx *gin.Context) {
    // 绑定JSON请求体到文章结构
    var article models.Article
    if err := ctx.ShouldBindJSON(&amp;amp;article); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return
    }

    // 自动迁移表结构
    if err := global.Db.AutoMigrate(&amp;amp;article); err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return
    }

    // 创建文章记录
    if err := global.Db.Create(&amp;amp;article).Error; err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return
    }

    // 删除缓存以保证数据一致性
    if err := global.RedisDB.Del(cacheKey).Err(); err != nil {
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return
    }

    // 返回创建的文章
    ctx.JSON(http.StatusCreated, article)

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;8.2 获取全部文章&lt;/h4&gt;
&lt;p&gt;优先从缓存中获取，如果获取不到再从数据库查询，并写入缓存。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func GetArticles(ctx *gin.Context) {
    // 尝试从Redis获取缓存数据
    cachedData, err := global.RedisDB.Get(cacheKey).Result()

    if err == redis.Nil {
        // 缓存不存在，从数据库查询
        var articles []models.Article

        if err := global.Db.Find(&amp;amp;articles).Error; err != nil {
            ctx.JSON(http.StatusInternalServerError, gin.H{
                &quot;error&quot;: err.Error(),
            })
            return
        }

        // 序列化文章数据
        articleJSON, err := json.Marshal(articles)
        if err != nil {
            ctx.JSON(http.StatusInternalServerError, gin.H{
                &quot;error&quot;: err.Error(),
            })
            return
        }

        // 设置Redis缓存，有效期10分钟
        if err := global.RedisDB.Set(cacheKey, articleJSON, 10*time.Minute).Err(); err != nil {
            ctx.JSON(http.StatusInternalServerError, gin.H{
                &quot;error&quot;: err.Error(),
            })
            return
        }
        ctx.JSON(http.StatusOK, articles)
    } else if err != nil {
        // Redis操作出错
        ctx.JSON(http.StatusInternalServerError, gin.H{
            &quot;error&quot;: err.Error(),
        })
        return

    } else {
        // 使用缓存的数据
        var articles []models.Article
        if err := json.Unmarshal([]byte(cachedData), &amp;amp;articles); err != nil {
            ctx.JSON(http.StatusInternalServerError, gin.H{
                &quot;error&quot;: err.Error(),
            })
            return
        }
        ctx.JSON(http.StatusOK, articles)
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;此项目结合了Gin框架和Gorm ORM，使用JWT进行用户身份验证，通过Redis缓存实现点赞功能，且配置通过Viper加载。项目结构简洁而清晰，代码通过分层结构便于维护，包含了基本的CRUD操作和API设计。&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
</content:encoded></item><item><title>借助本地部署的AI工具制作视觉小说</title><link>https://duke486.com/posts/%E5%80%9F%E5%8A%A9%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E7%9A%84ai%E5%B7%A5%E5%85%B7%E5%88%B6%E4%BD%9C%E8%A7%86%E8%A7%89%E5%B0%8F%E8%AF%B4/</link><guid isPermaLink="true">https://duke486.com/posts/%E5%80%9F%E5%8A%A9%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E7%9A%84ai%E5%B7%A5%E5%85%B7%E5%88%B6%E4%BD%9C%E8%A7%86%E8%A7%89%E5%B0%8F%E8%AF%B4/</guid><description>本文介绍了如何借助本地部署的AI工具，从零开始制作视觉小说，包括引擎、角色设定、剧情脚本、场景图、角色表情动作差分图、片头视频、片尾视频等内容。</description><pubDate>Mon, 09 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::warning
AI发展如此之快。本文撰写于2024年9月，所用工具和方法可能已经过时。&amp;lt;/br&amp;gt;
本文是应选修课程要求所写，因此内容较为肤浅，且未经过严格的校对和润色。
:::&lt;/p&gt;
&lt;h2&gt;一次有趣的实践&lt;/h2&gt;
&lt;p&gt;在校内选修了一门以“生成式AI”为主题课程，课程上讲授的一些内容与我的一个埋藏已久的点子不谋而合——从零开始开发一部视觉小说。鉴于此前已经有基于前端技术开发简单视觉小说应用的经验，再加上现在有了各领域都有了强大AI的帮助，因此我决定好好地尝试一次，从部署AI工具，到运行程序的搭建，完全从零开始用AI来实现每一步。&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;h2&gt;技术路线&lt;/h2&gt;
&lt;h3&gt;项目结构&lt;/h3&gt;
&lt;p&gt;首先需要明确的是，要制作基本完整的视觉小说作品，至少需要完成以下几个部分：引擎，角色设定，剧情脚本，场景图，角色表情动作差分图，片头视频（Opening），片尾视频（Ending）。接下来对这些必备元素稍做分析。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;引擎&lt;/strong&gt;是游戏的载体，是负责向用户展示故事情节与视觉小说基本功能（存取进度等）的程序。为了方便成品可以轻松运行、随处可用，同时简化开发难度，这里采用单页web应用做为游戏载体，借助Vue及前端技术栈开发。整个开发过程将会借助cursor、GitHub copilot等编程助手来实现。为了更完善的用户体验，也将同步推出基于Electron的Windows、MacOS桌面端应用（毕竟技术栈都一样）。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909212314-6liztoi.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;市场上的部分引擎&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;角色设定&lt;/strong&gt;及&lt;strong&gt;剧情脚本&lt;/strong&gt;是支撑游戏内容的重要部分，这部分将由本地LLM来完成创作。通过撰写完善的提示词与大致的剧情纲要，大语言模型有能力生成详尽的人物外观、性格、经历设定，并根据这些信息来帮助生成更符合角色特征的对白。最后依然借助本地大语言模型将对白数据结构化，导入到游戏文件中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;场景图&lt;/strong&gt;和&lt;strong&gt;角色差分图&lt;/strong&gt;，前者的制作较为简单，只需要向本地绘画模型描述（在comfy web UI中输入）大致内容即可。但是角色差分图的制作将会较为复杂，因为在整个游戏流程中，同一个角色的外貌特征应该有着极高的&lt;strong&gt;稳定性&lt;/strong&gt;，但是却又需要根据不同的情节调整&lt;strong&gt;表情、姿态&lt;/strong&gt;，这是主要难点。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909212049-jvnc92k.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;表情的局部差分&lt;/code&gt;​&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;角色稳定性方面主要有两种途径来解决，一是通过详尽的外貌提示词和较高的提示词权重来限制AI的创作，二是先使用一张基准图片搭配不同的提示词（img2img功能）来创建一组相似的该角色的图片，之后将图片组训练为Lora模型，在后续的生成中使用Lora来控制人物形象。&lt;/li&gt;
&lt;li&gt;表情可以使用蒙版工具（局部重绘）来修改，将底图与盖住脸部的蒙版同时导入，即可实现仅仅修改脸部表情。&lt;/li&gt;
&lt;li&gt;姿态可以使用ControlNet来控制，借助ControlNet和pose模型，我们可以将一张真人姿态照片应用到生成出的角色身上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，得益于国外&lt;a href=&quot;https://huggingface.co/&quot;&gt;HuggingFace&lt;/a&gt;与国内&lt;a href=&quot;https://www.liblib.art/&quot;&gt;LibLib&lt;/a&gt;等开源社区，许多Checkpoint、Lora和comfy UI工作流可以直接使用，使得角色风格更丰富更容易控制。在提示词超市（亦称为&lt;a href=&quot;https://tags.novelai.dev/&quot;&gt;标签超市&lt;/a&gt;、魔导书）中，也可以随心选择需要的提示词组合，更方便的控制和微调图像生成结果。&lt;/p&gt;
&lt;h3&gt;本地与云端大模型&lt;/h3&gt;
&lt;p&gt;本项目尽可能使用在本地部署的AI工具，仅在当本地端没有可以满足质量和效率要求的工具，或是工具所需显存过大的情况下才借助云端产品。主要环节实现途径如下表。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;实现途径&lt;/th&gt;
&lt;th&gt;任务&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;本地绘画模型&lt;/td&gt;
&lt;td&gt;绘制背景图、绘制角色立绘、绘制表情与动作差分图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;本地LLM&lt;/td&gt;
&lt;td&gt;撰写人设、完善故事细节、输出结构化对白数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suno.ai&lt;/td&gt;
&lt;td&gt;生成Opening、Ending音乐、生成剧情BGM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;人力&lt;/td&gt;
&lt;td&gt;将素材组织为Opening、Ending视频、搭建服务端&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909213002-oslk0sp.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;Suno音乐与歌曲生成&lt;/code&gt;​&lt;/p&gt;
&lt;h3&gt;提示词工程&lt;/h3&gt;
&lt;p&gt;在视觉小说的制作过程中，提示词的设计至关重要，因为它直接影响AI生成内容的质量与一致性。在为不同的素材撰写提示词时，需要根据目标内容的特点，确保提示词具备足够的&lt;strong&gt;清晰度、细节性&lt;/strong&gt;以及&lt;strong&gt;创意空间&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;角色设定&lt;/strong&gt;的提示词需要涵盖角色的外貌特征、性格、背景故事等多维度信息，确保AI生成的人物不仅外形符合预期，性格与行为逻辑也能融入剧情。提示词可以包含年龄、发型、服装风格等具体描述，同时通过性格词汇设定角色的情感表达与互动方式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;场景图&lt;/strong&gt;的提示词则更注重对氛围、色彩和空间布局的描述。场景不仅仅是视觉背景，还承载了故事的情感基调。因此，在撰写提示词时，可以明确指出时间、季节、光线效果等因素，帮助AI生成符合故事氛围的环境。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;角色表情与动作差分图&lt;/strong&gt;的提示词撰写需要特别注重情感和动态的表达。要确保不同的情绪（如愤怒、喜悦、悲伤）的细微差别能够通过提示词传达出来。这里需要通过明确的情感词汇和动作描写，如“微笑”、“皱眉”、“抬手”等，帮助AI准确抓住角色的情感和动作变化。&lt;/p&gt;
&lt;h2&gt;实际搭建&lt;/h2&gt;
&lt;h3&gt;comfy UI&lt;/h3&gt;
&lt;h4&gt;本体部署&lt;/h4&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909220028-eqm6uur.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;Comfy UI 的本体托管在 GitHub 上，我们可以通过 &lt;code&gt;git&lt;/code&gt;​ 来拉取最新版。首先，可以借助scoop等包管理器或官网MSI安装工具在系统中安装 Git 和 Python环境（3.8+），然后在终端中运行以下命令来克隆 Comfy UI 仓库并进入项目目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/comfyanonymous/ComfyUI.git
cd ComfyUI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着，通过以下命令安装所需依赖并启动 Comfy UI：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install -r requirements.txt
python main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动完成后，Comfy UI 将可以通过本地的 Web UI 进行访问，在 &lt;code&gt;http://localhost:8188&lt;/code&gt;​ 上。通过网页界面，我们就可以直接进行节点编辑、工作流制作和图像生成了。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909220210-pkyorbn.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h4&gt;插件管理器&lt;/h4&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909220410-rgk2vn1.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;Comfy UI 的许多功能和节点都依赖插件实现，但手动安装插件耗时较长，尤其是在加载新的工作流时需要安装大量节点。为了解决这个问题，可以借助 &lt;code&gt;manager&lt;/code&gt;​ 插件来简化安装与管理节点的流程。该插件允许一键安装缺失插件，自动更新已安装的插件，极大地提升了效率。使用时只需在插件管理器中选择相应的插件或节点库，系统会自动进行安装与升级。&lt;/p&gt;
&lt;p&gt;要安装，直接终端里输入以下命令（工作目录均为Comfy UI 根目录）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd custom_nodes
git clone https://github.com/ltdrdata/ComfyUI-Manager.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909220251-q0yy90j.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h4&gt;借助 Blender 的 Shade 中的节点样式 UI&lt;/h4&gt;
&lt;p&gt;虽然 Comfy UI 自带的 Web UI 功能强大，但在操作上略显局限，特别是在与本地文件系统的交互上不够便利。哔哩哔哩开源作者“一瓶辣椒酱”开发的 &lt;code&gt;铁锅炖&lt;/code&gt;​ 插件可以用 Blender 的节点样式 UI 替代 Comfy Web UI ，从而提升用户体验。&lt;/p&gt;
&lt;p&gt;Blender 等专业 3D 软件的材质和效果器编辑方式与 Comfy UI 的节点编辑方式十分相似。通过下载“铁锅炖”，并将插件安装至 Blender 目录下，我们可以使用 Blender 的直观 UI 来编辑 Comfy UI 中的节点，使操作更加便捷且直观。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;下载“铁锅炖” -&amp;gt; 首页安装插件至 Blender  -&amp;gt; 启动blender并切换的Comfy Node界面 -&amp;gt;连接Comfy UI Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909220851-5tvscjk.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h4&gt;Checkpoint 模型与 Lora&lt;/h4&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909221033-umgo11l.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;Comfy UI 支持加载 HuggingFace 和哩布哩布等平台上开源的 Checkpoint 模型和 Lora 模型。我们可以通过社区网站提供的模型预览和介绍选择需要的模型文件，下载后将其放入 Comfy UI 中的 &lt;code&gt;models&lt;/code&gt;​ 目录下的对应文件夹。然后，在 Comfy UI 的节点中加载这些模型，即可在工作流中使用不同的模型来生成不同风格的图像。具体路径如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/models/Checkpoints/   # Checkpoint 模型目录
/models/Lora/          # Lora 模型目录
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加载模型后，通过 UI 中的模型节点进行选择与应用，便可以将不同模型的风格应用到项目中。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909221130-ltb76jk.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h4&gt;工作流&lt;/h4&gt;
&lt;p&gt;在 HuggingFace 和哩布哩布等开源社区中，不仅可以下载模型，还可以找到许多功能强大的工作流模板。通过直接导入这些工作流，我们可以快速实现复杂的功能，而不需要从零开始搭建。例如，可以在社区中下载一个多节点的工作流，并通过 Comfy UI 导入，以完成图像生成、角色设定等任务。导入工作流时，Comfy UI Manager会检查所需的节点和插件是否完整，缺失的内容会提示你使用插件管理器一键安装。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909221247-i1ti585.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;平台浏览和下载&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909221349-p0j5nyr.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;本地一键导入、补齐缺失节点&lt;/code&gt;​&lt;/p&gt;
&lt;h3&gt;LLM&lt;/h3&gt;
&lt;p&gt;在本地部署大语言模型（LLM）是生成式AI开发中的一项重要步骤，我们可以在不依赖云端服务的情况下，直接在本地设备上运行强大的语言模型。在这里以Meta公司最新发布的 Llama 3.1 模型为例，在本地进行部署和运行。&lt;/p&gt;
&lt;h4&gt;硬件要求&lt;/h4&gt;
&lt;p&gt;首先需要确保电脑满足以下硬件要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;显卡&lt;/strong&gt;：NVIDIA RTX 3060 或更高&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;显存&lt;/strong&gt;：至少 8 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存&lt;/strong&gt;：至少 16 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬盘&lt;/strong&gt;：至少 20 GB 可用空间&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;安装 Ollama 客户端&lt;/h4&gt;
&lt;p&gt;为了在本地部署 Llama 3.1 模型，我们需要安装 &lt;strong&gt;Ollama&lt;/strong&gt; 客户端。该客户端不仅能让我们下载并运行 Llama 模型，还可以管理与模型相关的数据和文件。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909222814-kqs1gpz.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;首先，前往 &lt;a href=&quot;https://ollama.com/&quot;&gt;Ollama 官方网站&lt;/a&gt; 下载客户端并安装。默认安装目录为 &lt;code&gt;C:\Users\你的用户名\.ollama&lt;/code&gt;​。在这个目录下可以找到下载的模型文件和相关数据。&lt;/p&gt;
&lt;h4&gt;部署 Llama 3.1 模型&lt;/h4&gt;
&lt;p&gt;Ollama 客户端为我们提供了简单的命令行方式来安装和运行大语言模型。在终端中，使用以下命令来安装 &lt;strong&gt;Llama 3.1-8B&lt;/strong&gt; 模型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ollama run llama3.1:8b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个命令会在第一次运行时自动下载所需的模型文件，并将它们存储在默认目录中。&lt;strong&gt;Llama 3.1-8B&lt;/strong&gt; 模型需要至少 8GB 的显存来运行，适合个人开发者或中小型项目。&lt;/p&gt;
&lt;p&gt;如果你的计算资源较为有限，&lt;strong&gt;Llama 3.1-8B&lt;/strong&gt; 通常已经足够满足大部分任务需求。如果你需要更高性能或处理更复杂的任务，Ollama 还提供了更大模型（如 70B 和 405B）的下载和运行，但这些模型的硬件要求显著（真的很显著，高达450GB显存需求）增加。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909222930-gwbi5cc.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h4&gt;运行与交互&lt;/h4&gt;
&lt;p&gt;安装完成后，通过 Ollama 的终端可以直接与 Llama 3.1 模型进行交互。只需在终端中输入提示词，模型便会实时生成文本并进行反馈。对于那些更习惯使用类似 ChatGPT 图形界面的人，可以通过安装 &lt;strong&gt;GPT4ALL&lt;/strong&gt; 软件来获得一个用户友好的本地 LLM 启动器。&lt;/p&gt;
&lt;p&gt;然而，对于我们来说，使用终端与模型交互已经足够简单有效。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/%E6%97%A0%E6%A0%87%E9%A2%98-20240909223116-azg64bn.jpg&quot; alt=&quot;无标题&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;使用在线服务&lt;/h2&gt;
&lt;p&gt;非常简单，直接使用就好了，在此不再赘述。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Suno：进入官网，任选社交媒体账户登入即可获得免费额度。在create页面中输入需求即可创作。仅适合非大陆及港澳台地区。&lt;/li&gt;
&lt;li&gt;cursor：进入官网，下载安装客户端并从VS code迁移配置即可开始使用。引导教程涵盖补全、重写、整个项目作为上下文生成。&lt;/li&gt;
&lt;li&gt;GitHub Copilot：对于教育工作者和学生（大善人GitHub已收录本校），通过教育邮箱完成教育认证即可获得免费不限次的copilot使用权。在VS、VS code、Jetbrain系列IDE里面只需要安装copilot插件然后登陆一下账户即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240909223912-5ykxoi5.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;谁用谁知道，反正就是爽&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
</content:encoded></item><item><title>如何用自己的域名做https内网穿透</title><link>https://duke486.com/posts/%E7%94%A8%E8%87%AA%E5%B7%B1%E7%9A%84%E5%9F%9F%E5%90%8D%E5%81%9Ahttps%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/</link><guid isPermaLink="true">https://duke486.com/posts/%E7%94%A8%E8%87%AA%E5%B7%B1%E7%9A%84%E5%9F%9F%E5%90%8D%E5%81%9Ahttps%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/</guid><description>从零开始，让你的内网服务支持公网https访问</description><pubDate>Thu, 05 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;用自己的域名做https内网穿透&lt;/h1&gt;
&lt;h2&gt;为什么要做穿透&lt;/h2&gt;
&lt;p&gt;前段时间用黑裙nas折腾好了一些私有云服务，在内网提供emby、Komga、云同步、音乐库等服务。不过很快就遇到了一些问题，校内个别地方连通性不太好，管理给内网的设备之间做了隔离，因此无法在这些地方使用服务。此外，一旦离开内网环境，出门玩耍、实习的时候也没法愉快享受影音乐趣，很是不爽。&lt;/p&gt;
&lt;p&gt;看了多个大厂的穿透服务，性价比不能说很低，只能说没有。遂去海鲜市场和各小厂找解决方案。转了一圈发现&lt;a href=&quot;https://www.natfrp.com/&quot;&gt;Sakura FRP&lt;/a&gt;的性价比挺不错，操作起来也简单，支持导出frpc客户端配置。本次就以Sakura穿透服务为例聊一聊如何&lt;strong&gt;用自己的域名搭配内网穿透服务，并开启https安全连接&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/whyFRP-20240905220819-yfdntuj.jpg&quot; alt=&quot;whyFRP&quot; title=&quot;刚刚做好了FC社的宣传图，要怎么做才能让学长访问到呢？&quot; /&gt;​
&lt;code&gt;刚刚做好了FC社的宣传图，要怎么做才能让学长访问到呢？&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;开启隧道遇到的问题&lt;/h2&gt;
&lt;p&gt;Sakura对于备案要求比较严格，&lt;strong&gt;未备案&lt;/strong&gt;域名无法使用国内节点对外暴露80/443端口。使用自定义端口的TCP隧道时，节点于客户端之间必须开启https。&lt;/p&gt;
&lt;p&gt;这就产生的一个问题，Sakura一些节点的证书未被浏览器信任，直接通过节点域名+端口访问服务时，首先是浏览器会报不安全，非常烦人；其次是一些服务的客户端APP只认安全连接，没办法用。为了解决这些问题，我决定用自己的域名和zeroSLL证书来访问服务。&lt;/p&gt;
&lt;h2&gt;申请免费SSL证书&lt;/h2&gt;
&lt;p&gt;如果我们有自己的公网IP，那么则可以直接将域名解析到自己的IP或者套一层Cloudflare代理，随后通过&lt;a href=&quot;https://certbot.eff.org/&quot;&gt;Certbot&lt;/a&gt;直接自动向Let’s Encrypt之类的机构申请即可。但是我们现在的状况是：正因为没有公网IP，所以需要做内网穿透+域名访问，因此需要通过其他方法来获取证书。&lt;/p&gt;
&lt;p&gt;随着各大平台提供SSL证书有效期不断缩短，现在能获取到的免费证书基本都是只有90天有效期，需要手动续期，这里以Zero SSL为例。&lt;/p&gt;
&lt;p&gt;首先来到&lt;a href=&quot;https://zerossl.com/&quot;&gt;官网&lt;/a&gt;，直接在最显眼的输入框里输入我们要申请的域名，例如&lt;code&gt;sakura.yourdomain.com&lt;/code&gt;​，之后一路下一步。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240904160403-57cc858.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;随后，Zero SSL会要求你验证你是域名的所有者，这里采用DNS验证比较简单。首先来到DNS提供商的后台，这里依然使用Cloudflare。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;按照提示创建一条CNAME记录&lt;/li&gt;
&lt;li&gt;将网页提供的Name去掉主域名和顶级域名（即&lt;code&gt;yourdomain.com&lt;/code&gt;​这部分），保留子域名填入Cloudflare的&lt;code&gt;名称&lt;/code&gt;​一栏&lt;/li&gt;
&lt;li&gt;Point整个填入&lt;code&gt;目标&lt;/code&gt;​一栏&lt;/li&gt;
&lt;li&gt;小云朵代理一般不需要开启，保存即可&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240904160730-fprrhr2.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;:::tip&lt;br /&gt;
对于其他DNS提供商，也是类似的操作。例如你购买了阿里云的域名，则可以直接在阿里云控制台设置DNS解析。&lt;br /&gt;
:::&lt;/p&gt;
&lt;p&gt;完成操作后在浏览器中点击下一步等待自动验证即可，完成后就可以下载包含证书及私钥的压缩包了，之后将在frpc客户端中使用它。&lt;/p&gt;
&lt;p&gt;:::warning&lt;br /&gt;
续期过程中，用户可以选择继续使用原来的密钥对，或者生成新的密钥对。如果生成新的密钥对，证书中的公钥信息会改变！&lt;br /&gt;
:::&lt;/p&gt;
&lt;h2&gt;开通隧道&lt;/h2&gt;
&lt;p&gt;来到SakuraFRP后台，根据下图格式创建一个隧道，现在我们没有开放80/443端口的需求（要备案的&lt;code&gt;/(ㄒoㄒ)/~~&lt;/code&gt;​），所以可以不选带有“建站”标签的节点。&lt;/p&gt;
&lt;p&gt;图中的本地IP，如果你在同一台机器上运行服务端和穿透客户端，则选择&lt;code&gt;127.0.0.1&lt;/code&gt;​，否则填写服务端所在设备的内网IP（例如你决定在路由器或另一台内网设备上跑穿透）。本地端口为你的服务端所使用的端口。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240904162617-ed3963p.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;域名解析到节点&lt;/h2&gt;
&lt;p&gt;接下来需要将域名解析到Sakura提供的穿透节点，节点的域名可以把鼠标放上去查看。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240904163235-wkdnkax.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;知道了节点域名之后就可以在DNS页面创建一条CNAME记录，指向Sakura穿透节点即可，名称可以随意。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240904155200-rfog8ym.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;配置客户端并启动&lt;/h2&gt;
&lt;p&gt;在frpc客户端，我们需要添加证书和key。这里使用的是Sakura FRP启动器，它实际上是个frpc套壳，操作原理都是相同的。通过查阅SakuraFRP的wiki，可以得知群晖上的Sakura启动器工作目录的位置在&lt;code&gt;/var/packages/natfrp/var&lt;/code&gt;​。&lt;/p&gt;
&lt;p&gt;:::tip&lt;br /&gt;
对于其他平台也是同理，只需要在平台对应的配置目录做操作即可。在这里要吐槽一下Sakura的&lt;a href=&quot;https://doc.natfrp.com/launcher/manual.html&quot;&gt;wiki&lt;/a&gt;的导航和分类做的真的不太好，要查什么东西需要翻半天才能找到......&lt;br /&gt;
:::&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;核心服务的所有文件都是相对于 &lt;strong&gt;工作目录&lt;/strong&gt; 创建的。下面是工作目录的默认值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Windows：&lt;code&gt;%PROGRAMDATA%\SakuraFrpService&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;macOS：&lt;code&gt;~/Library/Containers/com.natfrp.launcher/Data/Library/Application Support/natfrp-service&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Linux / FreeBSD 下遵循 FreeDesktop 规范：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优先使用 &lt;code&gt;$XDG_CONFIG_HOME/.config/natfrp-service&lt;/code&gt;​&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;$XDG_CONFIG_HOME&lt;/code&gt;​ 未设置，则使用 &lt;code&gt;~/.config/natfrp-service&lt;/code&gt;​&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Android 启动器：&lt;code&gt;/data/data/com.natfrp.launcher/service/&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenWrt LuCI 插件：&lt;code&gt;/etc/natfrp&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;群晖 DSM 套件包：&lt;code&gt;/var/packages/natfrp/var&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Docker 镜像：&lt;code&gt;/run/&lt;/code&gt;​&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;我们来到工作目录中，进入FrpcWokingDirectory，将域名证书与私钥文件放在这里即可。我们在电脑上将之前在ZeroSSL下载好的zip文件打开，里面的crt文件和key文件使用文本编辑器打开后复制即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /var/packages/natfrp/var/FrpcWorkingDirectory
sudo vi xxx.yourdomain.com.crt
#输入你的群晖管理员密码后，粘贴crt证书中的内容
#按下Esc，输入 :wq ,按下回车即可保存
sudo vi xxx.yourdomain.com.key
#同上
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成后，即可在启动器中开启隧道。访问群晖IP+4101端口进入后台，在&lt;code&gt;隧道&lt;/code&gt;​页通过拖拽的方式启用隧道即可弹出提示：“隧道已启动，您可通过&lt;code&gt;https://节点域名:为你分配的端口&lt;/code&gt;​访问”。此时就可以使用&lt;code&gt;https://xxx.yourdomain.com:为你分配的端口/&lt;/code&gt;​来随处访问你的服务啦。&lt;/p&gt;
&lt;p&gt;:::tip&lt;br /&gt;
对于其他设备，则可以通过双击图标等更简单的方式来进入启动器页面，或者通过查阅Sakura的wiki、frpc文档、chatGPT来完成启动的步骤。在使用Sakura官方启动器之前，会要求通过token来进行登录，按照提示输入用户信息页面的访问密钥即可。&lt;br /&gt;
:::&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240905215429-ypg0z8w.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
</content:encoded></item><item><title>c++数据结构常用操作</title><link>https://duke486.com/posts/c%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C/</link><guid isPermaLink="true">https://duke486.com/posts/c%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C/</guid><description>一些常用的数据结构操作，包括数组、链表、Vector列表、栈、队列、双向队列、哈希表、树、堆等</description><pubDate>Sat, 29 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
参考文档见于文末
:::
在学习数据结构的过程中遇到了一些常用的STL操作和算法知识点，下面是整理后的结果。&lt;/p&gt;
&lt;h2&gt;数组&lt;/h2&gt;
&lt;p&gt;数组支持随机访问、占用内存较少；但插入和删除元素效率低，且初始化后长度不可变特点。&lt;/p&gt;
&lt;h3&gt;基本操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 初始化数组 */
// 存储在栈上
int arr[5];
int nums[5] = { 1, 3, 2, 5, 4 };
// 存储在堆上（需要手动释放空间）
int* arr1 = new int[5];
int* nums1 = new int[5] { 1, 3, 2, 5, 4 };
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;增删改&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 索引 index 处插入 num */
void insert(int *nums, int size, int num, int index) {
    // 把索引 index 以及之后的所有元素向后移动一位
    for (int i = size - 1; i &amp;gt; index; i--) {
        nums[i] = nums[i - 1];
    }
    // 将 num 赋给 index 处的元素
    nums[index] = num;
}

/* 删除 index 处 */
for (int i = index; i &amp;lt; size - 1; i++) {
    nums[i] = nums[i + 1];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;扩容&lt;/h3&gt;
&lt;p&gt;创建-&amp;gt;复制-&amp;gt;释放&lt;/p&gt;
&lt;h2&gt;链表&lt;/h2&gt;
&lt;p&gt;链表通过更改引用（指针）实现高效的节点插入与删除，且可以灵活调整长度；但节点访问效率低、占用内存较多。常见的链表类型包括单向链表、环形链表、双向链表。&lt;/p&gt;
&lt;h3&gt;数组与链表的效率&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;数组&lt;/th&gt;
&lt;th&gt;链表&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;存储方式&lt;/td&gt;
&lt;td&gt;连续内存空间&lt;/td&gt;
&lt;td&gt;分散内存空间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;容量扩展&lt;/td&gt;
&lt;td&gt;长度不可变&lt;/td&gt;
&lt;td&gt;可灵活扩展&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存效率&lt;/td&gt;
&lt;td&gt;元素占用内存少、但可能浪费空间&lt;/td&gt;
&lt;td&gt;元素占用内存多&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;访问元素&lt;/td&gt;
&lt;td&gt;𝑂(1)&lt;/td&gt;
&lt;td&gt;𝑂(𝑛)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;添加元素&lt;/td&gt;
&lt;td&gt;𝑂(𝑛)&lt;/td&gt;
&lt;td&gt;𝑂(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;删除元素&lt;/td&gt;
&lt;td&gt;𝑂(𝑛)&lt;/td&gt;
&lt;td&gt;𝑂(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;增删改&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 节点 n0 之后插入节点 P */
void insert(ListNode *n0, ListNode *P) {
    ListNode *n1 = n0-&amp;gt;next;
    P-&amp;gt;next = n1;
    n0-&amp;gt;next = P;
}

/* 删除 n0 之后的首个节点 */
void remove(ListNode *n0) {
    if (n0-&amp;gt;next == nullptr)
        return;
    // n0 -&amp;gt; P -&amp;gt; n1
    ListNode *P = n0-&amp;gt;next;
    ListNode *n1 = P-&amp;gt;next;
    n0-&amp;gt;next = n1;
    // 释放内存
    delete P;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Vector列表&lt;/h2&gt;
&lt;p&gt;列表（list）是一个抽象概念，它表示元素的有序集合，支持元素访问、修改、添加、删除和遍历等操作。列表可以基于链表或数组实现。&lt;/p&gt;
&lt;h3&gt;常用操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;//初始化
vector&amp;lt;int&amp;gt; nums1;
vector&amp;lt;int&amp;gt; nums = { 1, 3, 2, 5, 4 };

//末尾插入删除
nums.push_back(元素)；
nums.pop_back()；//动态数组为空时pop会出错

//中间插入删除
nums.insert(nums.begin() + 3, 6);  // 在索引 3 处插入 6
nums.erase(nums.begin() + 3);      // 删除索引 3 处

// 将列表 nums1 拼接到 nums 之后
nums.insert(nums.end(), nums1.begin(), nums1.end());

//长度
nums.size();

//排序
sort(nums.begin(), nums.end());

//清空释放内存
vector&amp;lt;类型&amp;gt;().swap(名字);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个列表类示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
class MyList {
  private:
    int *arr;             // 数组（存储列表元素）
    int arrCapacity = 10; // 列表容量
    int arrSize = 0;      // 列表长度（当前元素数量）
    int extendRatio = 2;   // 每次列表扩容的倍数

  public:
    /* 构造方法 */
    MyList() {
        arr = new int[arrCapacity];
    }

    /* 析构方法 */
    ~MyList() {
        delete[] arr;
    }

    /* 获取列表长度（当前元素数量）*/
    int size() {
        return arrSize;
    }

    /* 获取列表容量 */
    int capacity() {
        return arrCapacity;
    }

    /* 访问元素 */
    int get(int index) {
        // 索引如果越界，则抛出异常，下同
        if (index &amp;lt; 0 || index &amp;gt;= size())
            throw out_of_range(&quot;索引越界&quot;);
        return arr[index];
    }

    /* 更新元素 */
    void set(int index, int num) {
        if (index &amp;lt; 0 || index &amp;gt;= size())
            throw out_of_range(&quot;索引越界&quot;);
        arr[index] = num;
    }

    /* 在尾部添加元素 */
    void add(int num) {
        // 元素数量超出容量时，触发扩容机制
        if (size() == capacity())
            extendCapacity();
        arr[size()] = num;
        // 更新元素数量
        arrSize++;
    }

    /* 在中间插入元素 */
    void insert(int index, int num) {
        if (index &amp;lt; 0 || index &amp;gt;= size())
            throw out_of_range(&quot;索引越界&quot;);
        // 元素数量超出容量时，触发扩容机制
        if (size() == capacity())
            extendCapacity();
        // 将索引 index 以及之后的元素都向后移动一位
        for (int j = size() - 1; j &amp;gt;= index; j--) {
            arr[j + 1] = arr[j];
        }
        arr[index] = num;
        // 更新元素数量
        arrSize++;
    }

    /* 删除元素 */
    int remove(int index) {
        if (index &amp;lt; 0 || index &amp;gt;= size())
            throw out_of_range(&quot;索引越界&quot;);
        int num = arr[index];
        // 将索引 index 之后的元素都向前移动一位
        for (int j = index; j &amp;lt; size() - 1; j++) {
            arr[j] = arr[j + 1];
        }
        // 更新元素数量
        arrSize--;
        // 返回被删除的元素
        return num;
    }

    /* 列表扩容 */
    void extendCapacity() {
        // 新建一个长度为原数组 extendRatio 倍的新数组
        int newCapacity = capacity() * extendRatio;
        int *tmp = arr;
        arr = new int[newCapacity];
        // 将原数组中的所有元素复制到新数组
        for (int i = 0; i &amp;lt; size(); i++) {
            arr[i] = tmp[i];
        }
        // 释放内存
        delete[] tmp;
        arrCapacity = newCapacity;
    }

    /* 将列表转换为 Vector 用于打印 */
    vector&amp;lt;int&amp;gt; toVector() {
        // 仅转换有效长度范围内的列表元素
        vector&amp;lt;int&amp;gt; vec(size());
        for (int i = 0; i &amp;lt; size(); i++) {
            vec[i] = arr[i];
        }
        return vec;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;栈&lt;/h2&gt;
&lt;h3&gt;常用操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 初始化栈 */
stack&amp;lt;int&amp;gt; stack;

/* 入栈 */
stack.push(1);

/* 访问栈顶 */
int top = stack.top();

/* 元素出栈 */
stack.pop(); // 无返回值

/* 长度 */
stack.size();

/* 是否为空 */
stack.empty();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;手动实现&lt;/h3&gt;
&lt;p&gt;:::note&lt;br /&gt;
较为冗长，点击此处&lt;a href=&quot;https://www.hello-algo.com/chapter_stack_and_queue/stack/#1&quot;&gt;阅读&lt;/a&gt;&lt;br /&gt;
:::&lt;/p&gt;
&lt;h2&gt;队列、双向队列&lt;/h2&gt;
&lt;h3&gt;常用操作&lt;/h3&gt;
&lt;p&gt;队列&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 初始化 */
queue&amp;lt;int&amp;gt; queue;

/* 入队 */
queue.push(1);

/* 访问队首 */
queue.front();

/* 出队 */
queue.pop();

queue.size();
queue.empty();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;双向队列&lt;/p&gt;
&lt;p&gt;:::tip&lt;br /&gt;
兼具栈与队列的逻辑，&lt;strong&gt;因此它可以实现这两者的所有应用场景，同时提供更高的自由度&lt;/strong&gt;。&lt;br /&gt;
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deque&amp;lt;int&amp;gt; deque;

/* 入队 */
deque.push_back(2);   // 添加至队尾
deque.push_front(3);  // 添加至队首

/* 访问 */
deque.front(); // 队首元素
deque.back();   // 队尾元素

/* 出队 */
deque.pop_front();  // 队首元素出队
deque.pop_back();   // 队尾元素出队

/* 获取双向队列的长度 */
int size = deque.size();

/* 判断双向队列是否为空 */
bool empty = deque.empty();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实现&lt;/h2&gt;
&lt;p&gt;队列可以基于链表或者数组实现。基于链表时可以将列表的头尾节点分别视为队首和队尾，只在队尾插入只在队首删除；使用数组实现时只需要记录队首的索引、长度，通过公式计算队尾索引。&lt;/p&gt;
&lt;p&gt;当队伍移动到数组末尾时可以使用环形数组的存储方式，对队尾索引取模可得队尾实际位置。但是实现的队列仍然具有局限性：其长度不可变。&lt;/p&gt;
&lt;h2&gt;哈希表&lt;/h2&gt;
&lt;p&gt;负载因子（load factor）是哈希表的一个重要概念，其定义为哈希表的元素数量除以桶数量，用于衡量哈希冲突的严重程度，&lt;strong&gt;也常作为哈希表扩容的触发条件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;哈希表的结构改良方法主要包括 &lt;strong&gt;“链式地址”和“开放寻址”&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;链式地址&lt;/strong&gt;（separate chaining）将单个元素转换为链表，将键值对作为链表节点，将所有发生冲突的键值对都存储在同一链表中。当链表很长时，查询效率 𝑂(𝑛) 很差。此时可以将链表转换为“AVL 树”或“红黑树”，从而将查询操作的时间复杂度优化至 𝑂(log⁡𝑛) 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;开放寻址&lt;/strong&gt;（open addressing）不引入额外的数据结构，而是通过“多次探测”来处理哈希冲突，探测方式主要包括&lt;strong&gt;线性探测、平方探测和多次哈希&lt;/strong&gt;等。我们不能在开放寻址哈希表中直接删除元素。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;线性探测&lt;/strong&gt;采用固定步长的线性搜索来进行探测。过哈希函数计算桶索引，若发现桶内已有元素，则从冲突位置向后线性遍历（步长通常为 1 ），直至找到空桶，将元素插入其中​线性探测容易产生 &lt;strong&gt;“聚集现象”&lt;/strong&gt; 。具体来说，数组中连续被占用的位置越长，这些连续位置发生哈希冲突的可能性越大，从而进一步促使该位置的聚堆生长，形成恶性循环，最终导致增删查改操作效率劣化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;平方探测&lt;/strong&gt;与线性探测类似，都是开放寻址的常见策略之一。当发生冲突时，平方探测不是简单地跳过一个固定的步数，而是跳过“探测次数的平方”的步数，即 1,4,9,… 步。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多次哈希&lt;/strong&gt;方法使用多个哈希函数 𝑓1(𝑥)、𝑓2(𝑥)、𝑓3(𝑥)、… 进行探测。与线性探测相比，多次哈希方法不易产生聚集，但多个哈希函数会带来额外的计算量。&lt;/p&gt;
&lt;h3&gt;常用操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 初始化 */
unordered_map&amp;lt;int, string&amp;gt; map;

// 添加键值对 (key, value)
map[12836] = &quot;小哈&quot;;

// 查询value
string name = map[15937];

// 删除键值对 (key, value)
map.erase(10583);

/* 遍历哈希表 */
// 遍历键值对 key-&amp;gt;value
for (auto kv: map) {
    cout &amp;lt;&amp;lt; kv.first &amp;lt;&amp;lt; &quot; -&amp;gt; &quot; &amp;lt;&amp;lt; kv.second &amp;lt;&amp;lt; endl;
}
// 使用迭代器遍历 key-&amp;gt;value
for (auto iter = map.begin(); iter != map.end(); iter++) {
    cout &amp;lt;&amp;lt; iter-&amp;gt;first &amp;lt;&amp;lt; &quot;-&amp;gt;&quot; &amp;lt;&amp;lt; iter-&amp;gt;second &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;树&lt;/h2&gt;
&lt;h3&gt;术语&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;节点所在的层（level）：从顶至底递增，根节点所在层为 1 。&lt;/li&gt;
&lt;li&gt;节点的度（degree）：节点的子节点的数量。&lt;/li&gt;
&lt;li&gt;二叉树的高度（height）：从根节点到最远叶节点所经过的边的数量。&lt;/li&gt;
&lt;li&gt;节点的深度（depth）：从根节点到该节点所经过的&lt;strong&gt;边&lt;/strong&gt;的数量。&lt;/li&gt;
&lt;li&gt;节点的高度（height）：从距离该节点&lt;strong&gt;最远的叶节点&lt;/strong&gt;到该节点所经过的&lt;strong&gt;边&lt;/strong&gt;的数量。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;二叉树常用操作&lt;/h3&gt;
&lt;h4&gt;插入与删除节点&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;TreeNode* P = new TreeNode(0);
// 在 n1 -&amp;gt; n2 中间插入节点 P
n1-&amp;gt;left = P;
P-&amp;gt;left = n2;
// 删除节点 P
n1-&amp;gt;left = n2;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;a href=&quot;https://www.hello-algo.com/chapter_tree/binary_tree_traversal/#721&quot;&gt;层序遍历&lt;/a&gt;（BFS）&lt;/h4&gt;
&lt;p&gt;可以借助队列来实现&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;https://www.hello-algo.com/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png&quot; alt=&quot;二叉树的层序遍历&quot; /&gt;​&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 层序遍历 */
vector&amp;lt;int&amp;gt; levelOrder(TreeNode *root) {
    // 初始化队列，加入根节点
    queue&amp;lt;TreeNode *&amp;gt; queue;
    queue.push(root);
    // 初始化一个列表，用于保存遍历序列
    vector&amp;lt;int&amp;gt; vec;
    while (!queue.empty()) {
        TreeNode *node = queue.front();
        queue.pop();              // 队列出队
        vec.push_back(node-&amp;gt;val); // 保存节点值
        if (node-&amp;gt;left != nullptr)
            queue.push(node-&amp;gt;left); // 左子节点入队
        if (node-&amp;gt;right != nullptr)
            queue.push(node-&amp;gt;right); // 右子节点入队
    }
    return vec;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;前中后序遍历（DFS）&lt;/h4&gt;
&lt;p&gt;深搜借助递归来实现。二叉搜索树中进行中序遍历时，总是会优先遍历下一个最小节点，从而得出一个重要性质：&lt;strong&gt;二叉搜索树的中序遍历序列是升序的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240626195214-3195ixm.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 前序遍历 */
void preOrder(TreeNode *root) {
    if (root == nullptr)
        return;
    // 访问优先级：根节点 -&amp;gt; 左子树 -&amp;gt; 右子树
    vec.push_back(root-&amp;gt;val);
    preOrder(root-&amp;gt;left);
    preOrder(root-&amp;gt;right);
}

/* 中序遍历 */
void inOrder(TreeNode *root) {
    if (root == nullptr)
        return;
    // 访问优先级：左子树 -&amp;gt; 根节点 -&amp;gt; 右子树
    inOrder(root-&amp;gt;left);
    vec.push_back(root-&amp;gt;val);
    inOrder(root-&amp;gt;right);
}

/* 后序遍历 */
void postOrder(TreeNode *root) {
    if (root == nullptr)
        return;
    // 访问优先级：左子树 -&amp;gt; 右子树 -&amp;gt; 根节点
    postOrder(root-&amp;gt;left);
    postOrder(root-&amp;gt;right);
    vec.push_back(root-&amp;gt;val);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;p&gt;完全二叉树可以用数组来实现，但是不适合存储数据量过大的树，增删节点需要通过数组插入与删除操作实现，效率较低。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* 二叉搜索树查找 */
TreeNode *search(int num) {
    TreeNode *cur = root;
    while (cur != nullptr) {
        // 目标节点在 cur 的右子树中
        if (cur-&amp;gt;val &amp;lt; num)
            cur = cur-&amp;gt;right;
        // 目标节点在 cur 的左子树中
        else if (cur-&amp;gt;val &amp;gt; num)
            cur = cur-&amp;gt;left;
        else
            break;
    }
    // 返回目标节点
    return cur;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;AVL树、B树、红黑树&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;AVL树&lt;/strong&gt;（Adelson-Velsky and Landis树）是一种自平衡的二叉搜索树。它通过在每个节点上维护平衡因子（即左子树和右子树的高度差）来确保树的平衡，从而保证基本操作的时间复杂度为O(log n)。每次插入或删除操作后，AVL树会通过旋转操作来重新平衡树。&lt;/p&gt;
&lt;p&gt;与二叉搜索树不同，&lt;strong&gt;B树&lt;/strong&gt;的节点可以有多个子节点。适用于大规模数据存储，因为它可以高效地进行范围查询和顺序遍历，并且由于其节点可以存储多个键值对，它减少了需要访问磁盘的次数，从而提高了性能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;红黑树&lt;/strong&gt;（Red-Black Tree）是一种自平衡的二叉搜索树。它通过在每个节点上附加一个颜色属性（红色或黑色）来保持平衡，确保树的高度大致为O(log n)&lt;/p&gt;
&lt;p&gt;实现细节可以参考&lt;strong&gt;CS61B&lt;/strong&gt;的七海版教学：&lt;a href=&quot;https://www.bilibili.com/video/BV1eN411u7gr&quot;&gt;七海讲数据结构&lt;/a&gt;，质量很好也很有趣。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240626200517-776fh28.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;堆&lt;/h2&gt;
&lt;p&gt;需要指出的是，许多编程语言提供的是优先队列（priority queue），这是一种抽象的数据结构，定义为具有优先级排序的队列。&lt;/p&gt;
&lt;p&gt;实际上，&lt;strong&gt;堆通常用于实现优先队列，大顶堆相当于元素按从大到小的顺序出队的优先队列&lt;/strong&gt;。从使用角度来看，我们可以将“优先队列”和“堆”看作等价的数据结构。&lt;/p&gt;
&lt;h3&gt;常用操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 初始化堆 */
// 初始化小顶堆
priority_queue&amp;lt;int, vector&amp;lt;int&amp;gt;, greater&amp;lt;int&amp;gt;&amp;gt; minHeap;
// 初始化大顶堆
priority_queue&amp;lt;int, vector&amp;lt;int&amp;gt;, less&amp;lt;int&amp;gt;&amp;gt; maxHeap;

/* 元素入堆 */
maxHeap.push(1);

/* 获取堆顶元素 */
maxHeap.top(); 

/* 堆顶元素出堆 */
// 出堆元素会形成一个从大到小的序列
maxHeap.pop(); //c++中无返回值，python有

maxHeap.size();
maxHeap.empty();

/* 输入列表并建堆 */
vector&amp;lt;int&amp;gt; input{1, 3, 2, 5, 4};
priority_queue&amp;lt;int, vector&amp;lt;int&amp;gt;, greater&amp;lt;int&amp;gt;&amp;gt; minHeap(input.begin(), input.end());
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;p&gt;完全二叉树非常适合用数组来表示。由于堆正是一种完全二叉树，&lt;strong&gt;因此可以采用数组来存储堆。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;节点指针通过索引映射公式来实现&lt;/strong&gt;。给定索引 𝑖 ，其左子节点的索引为 2𝑖+1 ，右子节点的索引为 2𝑖+2 ，父节点的索引为 (𝑖−1)/2（向下整除）。当索引越界时，表示空节点或节点不存在。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240629135056-oxnizip.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;添加元素之后，由于 值 可能大于堆中其他元素，堆的成立条件可能已被破坏，&lt;strong&gt;因此需要修复从插入节点到根节点的路径上的各个节点&lt;/strong&gt;，这个操作被称为堆化（heapify）。从入堆节点开始，&lt;strong&gt;从底至顶执行堆化&lt;/strong&gt;。出堆时根节点开始，&lt;strong&gt;从顶至底执行堆化&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hello-algo.com/&quot;&gt;你好，算法！&lt;/a&gt;Github&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.cn/studyplan/selected-coding-interview/&quot;&gt;Krahets 笔面试精选 88 题&lt;/a&gt; LeetCode&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.cn/leetbook/read/illustration-of-algorithm&quot;&gt;图解算法数据结构&lt;/a&gt; LeetCode&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://book.douban.com/subject/35229404/&quot;&gt;大话数据结构&lt;/a&gt; Douban&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;‍&lt;/p&gt;
</content:encoded></item><item><title>最方便的 CLion 一键分别运行多个cpp方法</title><link>https://duke486.com/posts/%E5%9C%B0%E8%A1%A8%E6%9C%80%E5%BC%BA-clion%E4%B8%80%E9%94%AE%E5%88%86%E5%88%AB%E8%BF%90%E8%A1%8C%E5%A4%9A%E4%B8%AAcpp%E6%96%B9%E6%B3%95-cmd/</link><guid isPermaLink="true">https://duke486.com/posts/%E5%9C%B0%E8%A1%A8%E6%9C%80%E5%BC%BA-clion%E4%B8%80%E9%94%AE%E5%88%86%E5%88%AB%E8%BF%90%E8%A1%8C%E5%A4%9A%E4%B8%AAcpp%E6%96%B9%E6%B3%95-cmd/</guid><description>一种不借助任何插件的方法，实现在CLion中一键运行同一项目下的多个cpp文件</description><pubDate>Sat, 22 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note&lt;br /&gt;
文章标题所提到的是指中文互联网易于得到的搜索结果中，本方法是运行起来最为快捷的、准备起来最为简易并且一劳永逸的。不包含社区可能存在的更优解决方案。&amp;lt;/br&amp;gt;
这个方案主要适用于学习C++过程中，方便在同一个项目下运行不同的、带main函数的cpp文件。&lt;br /&gt;
:::&lt;/p&gt;
&lt;h2&gt;痛点所在&lt;/h2&gt;
&lt;p&gt;众所周知，CLion在创建项目后会自动给我们一个main.cpp，如果我们想在项目目录中同时存放多个cpp文件，会发现其他cpp的main函数是无法运行的。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240621233418-pqk5klu.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;目前网上已有的各种博客和解决方案无非是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在右上角的运行区域手动添加当前cpp文件的运行配置&lt;/li&gt;
&lt;li&gt;在Cmake配置文件中手动引入当前cpp文件，之后重载Cmake项目，从右上角运行区域选择需要运行的cpp&lt;/li&gt;
&lt;li&gt;在Cmake配置文件中写脚本或者直接全局引入&lt;code&gt;*.cpp&lt;/code&gt;​，在设置中勾选自动重载Cmake项目，最后从右上角运行区域选择需要运行的cpp&lt;/li&gt;
&lt;li&gt;安装插件来一键在Cmake配置文件中引入，从右上角运行区域选择需要运行的cpp&lt;/li&gt;
&lt;li&gt;安装插件来直接编译cpp，但是运行起来不方便&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是这些方案，我认为全都不够方便，都需要多次鼠标甚至是编辑文件操作，完全是浪费了效率，也不优雅。急需寻找一套不借助任何插件！&lt;/p&gt;
&lt;h2&gt;解决思路&lt;/h2&gt;
&lt;p&gt;借助脚本文件，将当前cpp文件传递给编译脚本，编译完成后在终端中执行exe成品&lt;/p&gt;
&lt;h2&gt;具体实现&lt;/h2&gt;
&lt;p&gt;:::warning&lt;br /&gt;
在CLion设置中进行任何编辑之后，应该立即点击应用按钮，否则极可能出错。必要的时候可以关闭设置页面重开。&lt;br /&gt;
:::&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;h3&gt;创建编译运行脚本&lt;/h3&gt;
&lt;p&gt;在不包含空格和中文的目录中创建&lt;code&gt;run_current.bat&lt;/code&gt;​，其中内容按照如下格式填写&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@echo off
setlocal

rem Get the filename without extension
set filename=%1
set target_name=%~n1

rem Navigate to the project directory
cd /d %~dp0

rem Compile the CPP file
你的编译器路径\mingw64\bin\g++ -o 你希望编译文件输出的位置\OUTPUT\%target_name% %filename%

if %errorlevel% equ 0 (
    rem Run the executable if the build was successful
    你希望编译文件输出的位置\OUTPUT\%target_name%
) else (
    echo Build failed
)

endlocal

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，&lt;code&gt;你的编译器路径&lt;/code&gt;​如果你不知道，可以打开&lt;code&gt;CLion&amp;gt;设置&amp;gt;构建执行部署&amp;gt;工具链&amp;gt;MinGW（默认）&amp;gt;工具集&amp;gt;下拉菜单&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;这里面会显示你的编译器路径，注意不要有空格，如果有需要在bat里面加引号：&lt;code&gt;&quot;你的编译器路径\mingw64\bin\g++&quot;&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;脚本的含义是接受cpp文件作为参数，然后在控制台编译并运行它。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240621235033-c50e95v.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;你应该提前创建好这个路径中的所有文件夹，不允许中文和空格：&lt;code&gt;你希望编译文件输出的位置\OUTPUT\&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;一切完成后保存，复制bat文件的路径，我这里以&lt;code&gt;D:\WORK\run_current.bat&lt;/code&gt;​为例。在你实践的时候记得替换成自己的目录。&lt;/p&gt;
&lt;h3&gt;向脚本传参&lt;/h3&gt;
&lt;p&gt;来到&lt;code&gt;CLion&amp;gt;设置&amp;gt;工具&amp;gt;外部工具&amp;gt;加号&lt;/code&gt;​，创建一个外部程序入口。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240621235410-boqwprg.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;名称随便写，程序填写&lt;code&gt;cmd.exe&lt;/code&gt;​。实参填写&lt;code&gt;/c &quot;D:\WORK\run_current.bat&quot; &quot;$FileDir$\$FileName$&quot;&lt;/code&gt;​，注意替换这里面的bat文件位置。工作目录填写&lt;code&gt;$ProjectFileDir$&lt;/code&gt;​。&lt;/p&gt;
&lt;p&gt;这里的意思是以当前项目目录为工作目录调用我们的编译运行脚本，同时把&lt;code&gt;cpp文件路径+文件名.cpp&lt;/code&gt;​作为参数传递到脚本中执行。&lt;/p&gt;
&lt;h3&gt;添加快捷键&lt;/h3&gt;
&lt;p&gt;来到&lt;code&gt;CLion&amp;gt;设置&amp;gt;按键映射&amp;gt;外部工具&amp;gt;外部工具&amp;gt;外部工具&amp;gt;双击&lt;/code&gt;​选择添加键盘快捷键，这里推荐&lt;code&gt;Alt+Shift+Z&lt;/code&gt;​，方便按还没有热键冲突。&lt;/p&gt;
&lt;h3&gt;消除“不属于任何项目目标”警告&lt;/h3&gt;
&lt;p&gt;由于CLion默认Cmake配置原因，除了main.cpp之外的cpp默认都不能开启代码洞察（如果你创建CPP文件的时候没勾选加入项目目标的话）。&lt;/p&gt;
&lt;p&gt;首先来到&lt;code&gt;CLion&amp;gt;设置&amp;gt;构建执行部署&amp;gt;Cmake&lt;/code&gt;​勾选自动重载Cmake项目：&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240622000419-6uzoqka.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;随后来到默认创建的&lt;code&gt;CMakeLists.txt&lt;/code&gt;​中，将&lt;code&gt;set(CMAKE_CXX_STANDARD XX)&lt;/code&gt;​这一行以下的内容删除，替换为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;file(GLOB SOURCES &quot;*.cpp&quot;)

add_executable(项目名 ${SOURCES})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;cmd控制台显示中文乱码&lt;/h2&gt;
&lt;p&gt;对于某些设备，用c++输出中文的时候，控制台都是乱码。造成此现象的原因是GBK于UTF8编码的转换问题。来到&lt;code&gt;CLion&amp;gt;设置&amp;gt;编辑器&amp;gt;文件编码&lt;/code&gt;​将全局改为GBK即可。&lt;/p&gt;
&lt;p&gt;对于已有的UTF8编码的cpp文件，可以打开它后双击&lt;code&gt;shift&lt;/code&gt;​，选择操作，输入&lt;code&gt;文件编码&lt;/code&gt;​，选择&lt;code&gt;文件编码（文件|文件属性）&lt;/code&gt;​。在弹出菜单中选择&lt;code&gt;GBK&lt;/code&gt;​，最后单击&lt;code&gt;转换&lt;/code&gt;​即可。&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;h2&gt;爽一把&lt;/h2&gt;
&lt;p&gt;在项目里面任意位置新建一个cpp，随便写点代码，按一下&lt;code&gt;Alt+Shift+Z&lt;/code&gt;​。终端瞬间就会开始编译执行你当前的cpp文件啦！如丝般顺滑~&lt;/p&gt;
&lt;p&gt;、&lt;img src=&quot;assets/66936860_p0-20240622001346-0salgwo.png&quot; alt=&quot;66936860_p0&quot; title=&quot;帅&quot; /&gt;​&lt;/p&gt;
</content:encoded></item><item><title>ACGN爽记-4-清晰视界:全平台视频超分插帧</title><link>https://duke486.com/posts/acgn%E7%88%BD%E8%AE%B0-4-%E6%B8%85%E6%99%B0%E8%A7%86%E7%95%8C%E5%85%A8%E5%B9%B3%E5%8F%B0%E8%A7%86%E9%A2%91%E8%B6%85%E5%88%86%E6%8F%92%E5%B8%A7/</link><guid isPermaLink="true">https://duke486.com/posts/acgn%E7%88%BD%E8%AE%B0-4-%E6%B8%85%E6%99%B0%E8%A7%86%E7%95%8C%E5%85%A8%E5%B9%B3%E5%8F%B0%E8%A7%86%E9%A2%91%E8%B6%85%E5%88%86%E6%8F%92%E5%B8%A7/</guid><description>通过MPV.net-DW和Reex播放器，与Anime4K算法搭配，在全平台实现视频超分。</description><pubDate>Mon, 20 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;概要&lt;/h2&gt;
&lt;p&gt;使用MPV.net-DW和Reex播放器，与Anime4K算法搭配，在全平台实现视频超分。效果如下图。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/anime4k-20240520152315-euc0a9k.png&quot; alt=&quot;anime4k&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;这次我们先不聊服务端，而是从播放器入手提升我们观看的幸福度。从感知上来说，无论我们的影片下载、收集、整理等步骤做得多好，如果没有一个好的播放器来呈现它们，我们实际的观看体验并不会好很多。很多朋友在折腾的过程中容易错把手段当做目的，实际上折腾的目标应该是为观看的幸福服务，而不是致力于提高NAS的参数、PT的流量等等。&lt;/p&gt;
&lt;p&gt;现在大多数番剧资源都是720P分辨率，新番也往往只有1080P高码，对于4K屏幕或追求画质的人来说实在有些不够，旧番全屏之后更是糊上加糊。因此为了能够以4K分辨率爽看古今动画，接下来介绍一种思路。&lt;/p&gt;
&lt;h2&gt;播放器安装&lt;/h2&gt;
&lt;p&gt;从系统自带播放器，到优酷、迅雷看看、暴风影音、VLC、potplayer，我在冲浪的路上遇到了许多播放器，每次相遇都有一种“我怎么没早点发现？”的感慨。现在这种感慨又来了。&lt;/p&gt;
&lt;p&gt;MPV作为开源项目，拥有众多分支，我们选用的是大佬做好的整合版MPV.net-DW。相比其他播放器，优势在于界面简洁美观、能正确渲染特效字幕、支持AI插帧、AI超分、支持HDR、支持杜比视界（据我所知PotPlayer直到最近才添加对杜比视界的官方支持）等等。接下来我们就看一看如何使用吧。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240520153906-xi8j51a.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;首先从github的&lt;code&gt;diana7127/mpv.net-DW&lt;/code&gt;​页面下载大佬打包的安装器，不过由于来自东方的神秘力量，我准备了能直接下载的链接。&lt;a href=&quot;http://duke486.pub:5244/d/data/mpv.net-DW_v2.0.0_Setup.exe?sign=aH0EvP_-bZlYn3qEacAlcBZ_uomEjXt8m-bZ57-jQAM=:0&quot;&gt;下载链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;安装完成后来到安装地址，打开播放器，右键画面进行设置。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240520154339-wvij2cc.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;在mpv设置中，&lt;code&gt;基础-&amp;gt;vo&lt;/code&gt;​选项，如果你是比较新的N卡可以改成&lt;code&gt;next&lt;/code&gt;​。&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;.NET专属-&amp;gt;start-size&lt;/code&gt;​选项，建议改成&lt;code&gt;session&lt;/code&gt;​，否则切换视频到时候窗口大小跳来跳去。&lt;/p&gt;
&lt;p&gt;之后我们找到一个视频文件，右键-&amp;gt;打开方式-&amp;gt;选择其他应用-&amp;gt;在电脑上选择应用-&amp;gt;选中刚才播放器文件-&amp;gt;点击“始终”就可以用新播放器双击打开视频了。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240520155333-vroew9f.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;开启超分和插帧&lt;/h2&gt;
&lt;p&gt;对于不同类型的视频，有不同的优化方法，我大致分为2D、3D、实拍三种来介绍。以下是插帧、超分功能的开启位置，具体选择什么可以自己尝试一下，图中是我推荐的选项。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240520155555-dsgtqsz.png&quot; alt=&quot;image&quot; title=&quot;插帧&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240520160540-bylhxkl.png&quot; alt=&quot;image&quot; title=&quot;超分&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;对于2D动漫来说，推荐开启&lt;code&gt;A4K HQ&lt;/code&gt;​，不开启插帧。&lt;/p&gt;
&lt;p&gt;对于3D视频，推荐同时开启二者，如果原视频本身就比较清晰，应该选择&lt;code&gt;A4K Fast&lt;/code&gt;​来避免卡顿。&lt;/p&gt;
&lt;p&gt;对于实拍视频，只建议开启插帧，&lt;code&gt;A4K&lt;/code&gt;​会丢失细节。&lt;/p&gt;
&lt;p&gt;通过右键设置的效果，在关闭窗口后不会保存，因此建议设置一下快捷键来方便一键开启这些效果。例如“按C开超”，“按V开插”。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240520161108-czjzjei.png&quot; alt=&quot;image&quot; title=&quot;快捷键编辑&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;手机平板超分&lt;/h2&gt;
&lt;p&gt;MPV官方播放器因为和高版本Android的兼容性问题，很难读到Anime4K配置文件。我们使用界面更美观，更方便的Reex播放器。&lt;a href=&quot;http://duke486.pub:5244/d/data/Reex%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%99%A8v1.8.4.apk?sign=Vt4K7o6EuqgL1FkeziUYFof4dyui5H_1s8E82vlOl8s=:0&quot;&gt;播放器下载地址&lt;/a&gt;，&lt;a href=&quot;http://duke486.pub:5244/d/data/Anime4K_v4.0.zip?sign=omY9KCjBdoMyDZLY2yIllNcMeTS3is5FqBDrorSAhig=:0&quot;&gt;Anime4K文件下载地址&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;将Anime4K解压到手机里，例如 &lt;code&gt;内部存储/shaders&lt;/code&gt;​。然后安装Reex，打开设置-&amp;gt;着色器-&amp;gt;加号，选择刚才shader文件夹里的所有文件。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/Screenshot_20240520_162531-20240520162628-wfab76z.jpg&quot; alt=&quot;Screenshot_20240520_162531&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;这里建议选择&lt;code&gt;DTD x2&lt;/code&gt;​，速度和效果比较均衡，当然其他的效果可以自己尝试。前，后对比如图。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240520162852-oyq45xp.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240520162915-y2w7d6s.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;结尾&lt;/h2&gt;
&lt;p&gt;超分拯救了低码率老番，就像近视患者带上眼镜一样，极大地提高了我们观看的幸福程度。不过这种粗暴加效果的方式可能会破坏压制组精心调教的画面效果，具体怎么选择就见仁见智咯，我个人是倾向于更高的分辨率，因为这是最直观最容易感受到的部分。本人才疏学浅，如果有不足之处欢迎留言！&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
</content:encoded></item><item><title>当代日式ACGN启发的立场体验装置</title><link>https://duke486.com/posts/%E5%BD%93%E4%BB%A3%E6%97%A5%E5%BC%8Facgn%E5%90%AF%E5%8F%91%E7%9A%84%E7%AB%8B%E5%9C%BA%E4%BD%93%E9%AA%8C%E8%A3%85%E7%BD%AE/</link><guid isPermaLink="true">https://duke486.com/posts/%E5%BD%93%E4%BB%A3%E6%97%A5%E5%BC%8Facgn%E5%90%AF%E5%8F%91%E7%9A%84%E7%AB%8B%E5%9C%BA%E4%BD%93%E9%AA%8C%E8%A3%85%E7%BD%AE/</guid><description>基于《你的名字》的立场体验装置设计，探讨物品作为连接人与人之间情感和理解的象征。</description><pubDate>Sun, 19 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;计算机学院&lt;/p&gt;
&lt;h2&gt;摘要&lt;/h2&gt;
&lt;p&gt;本研究通过分析当代日式 ACGN 作品，尤其是新海诚的《你的名字》，探讨了物品作为连接人与人之间情感和理解的象征。以此为灵感，提出了一个立场体验装置的设计概念，旨在促进深层次的人际理解和连接。该设计采用非人类中心主义视角，探索在技术进步和信息化社会背景下，人们如何通过设计促进真正的沟通和理解，从而解决现代社会中日益减少的人际连接和理解深度的问题。&lt;/p&gt;
&lt;p&gt;关键词：立场体验装置、ACGN 文化、《你的名字》、人际理解与连接、非人类中心主义、设计思辨&lt;/p&gt;
&lt;h3&gt;从作品出发&lt;/h3&gt;
&lt;p&gt;在众多 ACGN（动画、漫画、游戏、轻小说）作品中，无论是近代还是现代，大部分都在探讨社会中人与人之间的联系与理解。是的，自 20 世纪中叶起，以《铁臂阿童木》为代表的一众作品，无论其媒介如何，是否受到西方风格影响，都将人之间的联系作为自身的重要组成部分。在当代日式 ACGN 作品中，最令我印象深刻的当属作家新海诚的作品，尤其是《你的名字》。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image1-20240408123137-ea202rt.png&quot; alt=&quot;城市的风景 描述已自动生成&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;图 1 电影《君の名は。》主视觉图&lt;/p&gt;
&lt;p&gt;自 2016 年在内地上映以来，这部电影以其独特的叙事手法和深刻的主题意义，在全球范围内引起了广泛关注。电影通过一个关于灵魂互换的故事和一个特殊物品，探讨了心灵和肉体、时间和空间以及人际间微妙的连接，这种连接触及了人类深层的情感和认同。它通过描绘两位主角——身处不同地点的少年男女，因超自然现象相遇而彼此交换身体，体验对方的生活——来展示这种连接和沟通的方式 1。《你的名字》不仅是一次视觉的享受，更是一次深刻的心灵旅程。它呈现了人们在追求理解和被理解的过程中所的重重阻碍。主角的经历，让我看到了理解和连接怎样发生，以及如何促使人们跨越时间和空间的障碍，感受彼此。身体互换不仅仅是一种剧情设定，更象征着深层次的心灵交换，提示着我们在现实世界中也应寻求更深刻的人际连接和理解。&lt;/p&gt;
&lt;p&gt;新海诚 2 作为作家，对“物品对人心的象征意义”的描写似乎已经成为他的风格，这在他的作品中随处可见。链接角色的特殊物品在《云之彼端，约定的地方》中是自制飞机 Velaciela 和小提琴；在《秒速五厘米》中是花瓣和电车；在《追逐繁星的孩子》中是则歌薇丝矿石。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image2-20240408123137-utg0nom.png&quot; alt=&quot;日历 低可信度描述已自动生成&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;图 2Velaciela 上的重逢&lt;/p&gt;
&lt;p&gt;除此之外，日式 ACGN 文化中不乏以物品为寄托探讨人与人之间连接的作品。例如，《星灵感应》通过模型火箭这一载体，讲述了同学之间心灵感应、建立友谊的故事，探讨了物品如何成为人类情感和记忆交换的载体。而《玉子市场》中的纸杯电话则象征着青梅竹马之间的互相的了解与交流。这些作品通过物品与人之间的互动，展现了日式 ACGN 中独有的对于人际连接和心灵理解的深刻见解。在本设计中采用的是《你的名字》中的元素——宫水家族的传统绳结（組紐くみひも）。这些绳结不仅是家族传统的体现，也象征着人与人之间的连接与纽带。这种物质文化中的象征意义，与产品主题形成了呼应。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image3-20240408123137-m1o1m8r.png&quot; alt=&quot;男人和女人在路上走 描述已自动生成&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;图 3 近在咫尺，依然使用纸杯电话&lt;/p&gt;
&lt;p&gt;通过以上展示的典型作品，我们可以看到，在日式 ACGN 文化中，物品不仅仅是故事中的道具，它们成为了连接人们内心世界的桥梁。这些作品通过精心设计的情节和信物，引导观众反思现实世界中人与人之间的联系与理解，激发了对更加深入人际理解和沟通方式的思考。在这样的背景下，探索如何通过设计来促进人们之间的理解和连接，成为了一个值得深入研究的课题。&lt;/p&gt;
&lt;h3&gt;寻找答案&lt;/h3&gt;
&lt;p&gt;在寻找设计灵感时，我发现《你的名字》稍不同于其他日式 ACGN 作品中那些微妙的人物关系和物品象征，它强调了深层的文化背景、在时间和空间上的跨度、见证了从陌生到联结的全过程，因此組紐 3 顺理成章地成为了立场体验装置的原形参考。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image4-20240408123137-jlpu8xy.png&quot; alt=&quot;图片包含 游戏机, 布, 地毯 描述已自动生成&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;图 4 传统技艺在现代&lt;/p&gt;
&lt;p&gt;问题的核心在于，尽管我们生活在一个技术日益进步、信息流通日益加快的时代，人与人之间真正的理解和深刻的连接似乎却在逐渐流失。这引出了一个深层次的探询：在现代社会中，人们是否还能够真正理解彼此？这个问题并不是留存与字面上的，它涉及到人类交流的本质、技术对人际关系的影响，以及我们如何在日益繁忙的生活中寻找意义和连接。&lt;/p&gt;
&lt;p&gt;为了深入探讨这一问题，我选择了非人类中心主义 4 这一概念作为理论支撑。这一观点挑战了传统以人类为中心的世界观，提醒我们重新思考人类与自然、以及人与非人类实体之间的关系。通过这个视角，我开始思考设计不仅仅是满足人类需求的工具，更是一种促进所有生命体之间理解和和谐共存的手段。这种思考方式为我的设计带来了全新的视角，使我不再局限于人类自身，而是扩展到了更广阔的存在和多样性中，也即“物品”的形式。&lt;/p&gt;
&lt;p&gt;在设计的物质选择上，宫水三叶家族的绳结作为装置的原型，在《你的名字》中，绳结不仅是家族传承的工艺品，更象征着时间、记忆以及人与人之间的不解之缘。这种传统的物品富含深厚的文化意义和情感价值，正是我想在设计中寻求的那种能够触动人心、促进深层次理解和连接的元素。通过将这种象征性的物品融入设计，我的目的是创造一个不仅仅是技术上的创新，而是能够触及人们内心、唤起共鸣和理解的装置。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image5-20240408123137-0yhcyd3.png&quot; alt=&quot;卡通人物 描述已自动生成&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;图 5 绳结在无形之中连接&lt;/p&gt;
&lt;p&gt;立场体验装置作为一个促进理解、启发思考的平台，核心功能是使使用者能够体验到其他人的感受和立场，这种体验超越了语言和文化的界限，触及了人类共有的情感和认知深层。我的设计目标是通过这种直观的体验，消除误解和偏见，建立更加深刻和真实的人际连接。&lt;/p&gt;
&lt;p&gt;这个设计方案背后的意图并不仅仅是解决一个问题。我希望通过公众对这个装置的使用和体验，引发更广泛的社会讨论和反思。真正的理解是否可能？我们在追求技术进步的同时，是否忽略了人类情感和精神世界的深层需要？结合假设法和未来椎体模型，设计中还可能带来的思想上的隐私问题，以及在深层次的心灵连接中，精神污染的风险是否存在？儿童是否适合于成人共享内心？这些问题的探讨，使这个设计项目超越其物理形态，成为一个触发社会思考和对话的媒介。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image6-20240408123137-znepvj2.png&quot; alt=&quot;&quot; /&gt;
图 6 产品概念图&lt;/p&gt;
&lt;h3&gt;研究总结&lt;/h3&gt;
&lt;p&gt;作品成为了我思辨设计的窗口。通过这种设计探究，我的立场体验装置旨在不仅仅是解决人们理解不足的问题，更重要的是激发人们对于人际关系、技术发展和个人内心世界的深入思考。在这个过程中，设计本身成为了一种语言，一种促进人类更加深刻相互理解和共情的语言，同时也是一个探索未来社会可能性的工具。在未来椎体模型 5 中，我相信我的设计可以拓展 Possible,提高 probable 在 plausible 中的占比，以求创造更美好的未来。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image7-20240408123137-uhq10ro.png&quot; alt=&quot;卡通人物 描述已自动生成&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;图 7 链接彼此-通向更好未来&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image8-20240408123137-k9ptnbk.png&quot; alt=&quot;图示 低可信度描述已自动生成&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;图 8 思维导图&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E4%BD%A0%E7%9A%84%E5%90%8D%E5%AD%97%E3%80%82&quot;&gt;你的名字。 - 维基百科，自由的百科全书&lt;/a&gt;[2024-3-18]&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.moegirl.org.cn/%E6%96%B0%E6%B5%B7%E8%AF%9A&quot;&gt;新海诚 - 萌娘百科 万物皆可萌的百科全书&lt;/a&gt;[2023-10-12]&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ja.wikipedia.org/wiki/%E7%B5%84%E3%81%BF%E7%B4%90&quot;&gt;組み紐 - Wikipedia&lt;/a&gt;[2023-8-7]&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.airitilibrary.com/Article/Detail/10158383-200708-34-8-125-151-a&quot;&gt;人類中心主義與非人類中心主義辨難-王海明《哲學與文化》&lt;/a&gt;[2007-8]34 卷 8 期 P. 125-151&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://liweiyu.medium.com/no-1-%E6%9C%AA%E4%BE%86%E6%80%9D%E7%B6%AD-%E4%B8%80%E7%A8%AE%E5%BF%83%E6%85%8B-%E4%B8%8D%E6%98%AF%E6%96%B9%E6%B3%95-275dc1d50570&quot;&gt;未来思维：一种心态，不是方法 - Design Research Design -&lt;/a&gt;[2020-12]&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;‍文中对于作品的引用和图片来自公开数据库和网络平台，如有侵权请通过邮箱联系。&lt;/p&gt;
</content:encoded></item><item><title>宫崎骏谈《千与千寻》</title><link>https://duke486.com/posts/%E5%AE%AB%E5%B4%8E%E9%AA%8F%E8%B0%88%E5%8D%83%E4%B8%8E%E5%8D%83%E5%AF%BB/</link><guid isPermaLink="true">https://duke486.com/posts/%E5%AE%AB%E5%B4%8E%E9%AA%8F%E8%B0%88%E5%8D%83%E4%B8%8E%E5%8D%83%E5%AF%BB/</guid><description>在采访中，宫崎骏谈及《千与千寻》的创作初衷，以及对现代社会的思考。</description><pubDate>Sat, 18 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;宫崎骏谈《千与千寻》&lt;/h1&gt;
&lt;p&gt;:::note
摘自《The Art of Spirited Away-千与千寻官方艺术设定集》&amp;lt;br&amp;gt;
北京联合出版公司出版&amp;lt;br&amp;gt;
图像来源 IMDB 如有侵权请邮件联系删除
:::
这是一个冒险故事。虽然故事里并没有出现什么神奇的武器或超能力，但它依然是一个冒险故事。只不过这个冒险故事并非以正邪对决为主题，而是讲述一名少女被扔在一个好人与坏人混杂的世界中，如何修行、学习友爱与奉献，并运用自己的智慧生存下来。最终她能够突破难关、逃脱劫难，回到原来的生活中，并不是因为恶被消灭了——恶是与这世间一样永存的，而是因为她获得了生存的能力。&lt;/p&gt;
&lt;p&gt;当今社会，善恶变得暧昧不明，明明如此含糊，却又有着吞没与消费一切的欲望。而这部电影的主题，便是借用奇幻的体裁，将这样的社会清楚地描绘出来。&lt;/p&gt;
&lt;p&gt;现在的小孩子们，在日常的生活中被圈养、被守护、被疏远，以至于对“活着”这件事只有一些稀薄而模糊的感受，他们只能让孱弱的自我越发膨胀。千寻软弱无力的手脚，经常挂着看什么都无聊的苦瓜脸，这些便是现在孩子们的象征。但是，当现实变得清晰，当她进退维谷而不得不直面危机的时候，一股她自己都不曾察觉的适应力与忍耐力涌现出来，她终于发现，她所拥有的生命，能够发挥出多么果断的判断力与行动力。&lt;/p&gt;
&lt;p&gt;其实，大部分人遇事时只会陷入慌乱，反复咒骂着瘫坐在地上。而这样的人在落入千寻的处境时，恐怕很快就会被环境消灭掉，或者被强者吃掉吧。千寻之所以能成为主人公，绝不是因为她是个美少女，或者拥有多么稀世的灵魂，而是因为她拥有不为他人所吞噬的力量。这一点，才是这部作品的过人之处，所以也就不难理解，它得以成为一部给十来岁的女孩子们看的电影。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240518120805-969zgsp.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;语言就是力量。在千寻所迷失的世界中，说话这件事是极有分量的，足以造成无法挽回的结果。在被汤婆婆支配的澡堂，只要说一句“我不愿意”“我想回家”，千寻当场就会被魔女赶出去。那时的她，要么流离失所最终在徘徊中消亡，要么变成不停下蛋的鸡直到被吃掉为止。相反，只要千寻说出“在这里工作”这句话，即使是魔女也不能无视她。现如今，人们将语言视为气泡般轻浮的行为，可以张口就来，而这不过是现实日益虚化的表现。语言依然是有力的，至今也是真理，只不过到处充斥的都是无力的空虚之词而已。&lt;/p&gt;
&lt;p&gt;把他人的名字夺走，并不是要改变称呼，而是要完完全全地支配对方。小千毛骨悚然地发现，连她自己都在逐渐遗忘“千寻”这个名字。随着每次去猪舍，她也渐渐习惯父母变成了猪的模样。在汤婆婆的世界里，必须时时刻刻在被吃掉的危机中挣扎才能活下去。&lt;/p&gt;
&lt;p&gt;在如此艰难的世间，千寻反而越来越有生气。电影的大团圆结局中，原本皱着一张苦瓜脸的角色，最后也会露出充满魅力、豁然开朗的表情。直到今天，这个世界的本质依然没有任何改变。这部电影就是要用自身的说服力向大众表明：语言就是意志，就是自我，就是力量。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240518120858-muv540s.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;选择以日本为舞台创作一部奇幻作品的意义也在于此。即使是童话故事，我也不想选那种退路多多的西欧童话风格。大家普遍觉得这部电影是效仿了常见的异世界故事，可我更愿意视其为《麻雀之家》或《老鼠的宫殿》的直系子孙。不说什么平行世界，我们的祖先本也是今天在麻雀的家里栽跟头、明天在老鼠大人的宴会上吃喝玩乐这么一路走来的。&lt;/p&gt;
&lt;p&gt;汤婆婆居住的世界被设定成了仿欧风，为的是让人觉得似曾相识，从而产生似幻似真的感觉。同时，也是因为有日本传统造型这座宝库，为我们的想象提供了无尽的原料。民俗的空间——传说、传承、习俗、造型、祭祀甚至是咒术——如此丰富而独特，只是还不为大众所知。咔嚓咔嚓山和桃太郎这种故事已经无法让如今的孩子信服了。同时，难道包罗万象的传统艺术，就一定要局限在传统民间故事这么一个芝麻绿豆大小的世界中吗？不得不说，这就是一种想象力贫瘠的表现。如今的小孩子被高科技包围，在各种单薄的工业制品中渐渐失去了他们的根。所以必须传达给他们——我们拥有怎样丰富的传统。&lt;/p&gt;
&lt;p&gt;我希望将传统的工艺与现代人愿意看的故事相结合，成为一片色彩斑斓的马赛克，镶嵌进电影的世界中，使其获得新鲜的认同感。同时，这也是我们对自己岛国居民身份的重新认知。&lt;/p&gt;
&lt;p&gt;因为在这个无国界的时代，找不准自己定位的人，是最为别人所轻视的。你的定位就是你的过去、你的历史。没有历史的人、遗忘过去的民族，要么流离失所最终在徘徊中消亡，要么变成不停下蛋的鸡直到被吃掉为止。&lt;/p&gt;
&lt;p&gt;愿这部作品能够让那些十来岁的女孩子观众领悟自己真正的期望。我想做的，就是这样的电影。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240518120946-nh6o8or.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
</content:encoded></item><item><title>ACGN爽记-3-Komga、Mihon全平台漫画</title><link>https://duke486.com/posts/acgn%E7%88%BD%E8%AE%B0-3-komgamihon%E5%85%A8%E5%B9%B3%E5%8F%B0%E6%BC%AB%E7%94%BB/</link><guid isPermaLink="true">https://duke486.com/posts/acgn%E7%88%BD%E8%AE%B0-3-komgamihon%E5%85%A8%E5%B9%B3%E5%8F%B0%E6%BC%AB%E7%94%BB/</guid><description>安装Komga服务端，使用Mihon客户端+插件实现多设备漫画阅读</description><pubDate>Fri, 10 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::warning&lt;br /&gt;
2024年6月起，由于一些原因，Docker镜像服务器的连通性存在问题，请确认您的网络质量。&lt;br /&gt;
:::&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在准备系列教程之初，我就计划将漫画库放在前面讲，因为它真的极大地改善了我追漫、收藏电子漫画的幸福感和方便程度。但是在 24 年初的时候，开源漫画阅读器项目 Tachiyomi 面临法务危机，其项目自身和众多第三方插件的后续更新都得不到保障，教程也就之好先略过这部分不讲。&lt;/p&gt;
&lt;p&gt;不久后 Tachiyomi 的开发者将项目归档，并在 wiki 中发布了告别信，事件算是尘埃落定。不过好在开源社区有着极大的活力，仅仅数日后 Discord 上便成立了 Mihon 项目的发布服务器。作为 Tachiyomi 的后继者，Mihon 青出于蓝，不仅做到了全面向后兼容，在 UI 设计和稳定性上也积极更新。同时，作为阅读器灵魂所在的第三方插件仓库也得到了维护，目前来看 Mihon 已经稳定更新，适合用来日用了。&lt;/p&gt;
&lt;h2&gt;本期目标&lt;/h2&gt;
&lt;p&gt;这次我们将通过 Docker 来部署 Komga 漫画服务，以便在浏览器、手机、平板等设备上无缝阅读。推荐的方式是通过面板应用商店一键安装，不过对老司机，也会在文末提供 Docker Compose 配置供直接安装。&lt;/p&gt;
&lt;p&gt;这次的效果图如下，多设备同时共享同一个漫画库：&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/Screenshot_20240510_175150_Tachiyomi-20240510175226-giuzap8.jpg&quot; alt=&quot;Screenshot_20240510_175150_Tachiyomi&quot; title=&quot;平板端&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240510164108-qnno5xu.png&quot; alt=&quot;image&quot; title=&quot;网页端&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/Screenshot_2024-05-10-16-42-56-453_app.mihon-20240510164456-abc3zhb.jpg&quot; alt=&quot;Screenshot_2024-05-10-16-42-56-453_app.mihon&quot; title=&quot;手机端&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;在服务端安装 Komga&lt;/h2&gt;
&lt;p&gt;来到我们之前设置过的面板后台，格式是 &lt;code&gt;http://IP地址:端口/安全入口&lt;/code&gt;​，如果忘记了可以在 Ubuntu 终端或者 SSH 中输入 &lt;code&gt;sudo 1pctl user-info&lt;/code&gt; ​来获得一个地址。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;什么是终端、SSH？不妨看看教程第1篇吧。&lt;a href=&quot;http://duke486.pub:5212/archives/59&quot;&gt;阅读&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;进入后台，在应用商店中搜索 komga 然后点击安装，记得勾选允许外部端口访问。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240510170016-wlc2sox.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;安装后来到容器页面，勾选“显示应用商店容器”，对着 komga 点击更多 &amp;gt; 编辑 &amp;gt; 挂载 &amp;gt; 添加 &amp;gt; 本机目录。第一个框填写 &lt;code&gt;/home/你的用户名/comic&lt;/code&gt;​ 第二个空填写 &lt;code&gt;/comic&lt;/code&gt;​，保存即可。&lt;/p&gt;
&lt;h2&gt;导入已有漫画&lt;/h2&gt;
&lt;p&gt;无论是 Komga 还是 Mihon，对于漫画的目录结构都有一定要求。简单来说就是放漫画的文件夹里面，要有不同的作品文件夹，每个作品文件夹里面有 &lt;code&gt;PDF、EPUB、ZIP、装在文件夹里的图片&lt;/code&gt; ​等章节文件。例如，如果你和我一样用 comic 文件夹当做漫画库：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;/comic/星电感应/第 1 话.cbz&lt;/p&gt;
&lt;p&gt;/comic/你的名字/设定集/001.png&lt;/p&gt;
&lt;p&gt;错误例子：/comic/葬送的芙莉莲 001.zip&lt;/p&gt;
&lt;p&gt;/黄金拼图第 1 话.mobi&lt;/p&gt;
&lt;p&gt;/comic/你的名字/你的名字第一话/你的名字第一话/第一话.cbz&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;方法一：Alist 上传。打开容器面板，选择左侧的容器，对着 Alist 点击更多-&amp;gt; 编辑-&amp;gt; 挂载，在这里设置目录映射关系。它自带的不需要动，我们直接添加&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;本机：/home/你的用户名/comic&lt;/code&gt;​&lt;br /&gt;
​&lt;code&gt;容器：/mnt/comic&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;然后登录 Alist ，设置一个新的本地存储，把漫画上传进 comic 就好，这种方法以后上传都比较方便&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法二：面板直接上传。首先把漫画打包，然后在面板中打开主机 &amp;gt; 文件 &amp;gt; 找到 &lt;code&gt;/home/用户名/comic&lt;/code&gt;​&amp;gt; 上传压缩包 &amp;gt; 解压到此&lt;/p&gt;
&lt;p&gt;这种方法最简单，但是每次导入都得这样操作一遍。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法三：FTP/SFTP 客户端&lt;/p&gt;
&lt;p&gt;在你的 SSH 软件里找到关于文件管理的按钮，直接把电脑里的漫画拖进去。例如Tabby就支持此功能。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;应用内设置&lt;/h2&gt;
&lt;p&gt;点击快捷入口按钮进入 Komga 网页版，根据提示设置一下账号密码。点击“添加库”按钮，根文件夹设置为&lt;code&gt;/comic&lt;/code&gt;​ 然后一路下一步即可。如果你是的机器性能不太够，可以取消勾选“计算文件哈希”，这个功能影响不大。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240510171428-399z019.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240510171646-04blfse.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;最后我们点一下扫描，自动分析就开始了，稍等就可以啦。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240510174131-3gbxfke.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240510191546-j2w34dq.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;h2&gt;移动端阅读&lt;/h2&gt;
&lt;p&gt;由于涉及到 Github 的 Mihon 和 Keiyoushi 仓库下载，但是由于一股神秘的东方力量，一些读者的网络连接不畅，所以我准备了能点开的下载链接。&lt;/p&gt;
&lt;p&gt;iPhone 和 iPad 可以参考官方 wiki 安装软件，步骤相对简单，在此不在赘述（其实是我没有苹果手机，哭）。&lt;a href=&quot;https://komga.org/docs/guides/panels&quot;&gt;苹果教程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Mihon 本体：&lt;a href=&quot;http://duke486.pub:5244/d/data/mihon-v0.16.5.apk?sign=R4fqg1uUupVkZ-_D6mHJZv7v_RhGY0UBDOBtfx6Kppw=:0&quot;&gt;下载&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Komga 插件：&lt;a href=&quot;http://duke486.pub:5244/d/data/tachiyomi-all.komga-v1.4.57.apk?sign=TSB8XiSUDALZHc37wEiLufrE2qrv3SgMPctZKP8t978=:0&quot;&gt;下载&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;全部安装好后打开 Mihon，选择存储位置时在手机根目录新建文件夹，起名为 Mihon，然后选择“使用此文件夹”。其他设置按照喜好选就好了。​&lt;/p&gt;
&lt;p&gt;选择浏览，插件，对着红色的 Komga 点击一下，信任他。之后点击 Komga 旁边的设置图标，关闭 Komga2 和 Komga3。点击 Komga(ALL)旁边的设置图标，在 address 里面填入 &lt;code&gt;http://IP地址:25600&lt;/code&gt; ​username 和 password 则是你设置的账号密码，填写后返回即可。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/Screenshot_20240510_175658_Mihon-20240510175745-di9p0sn.jpg&quot; alt=&quot;Screenshot_20240510_175658_Mihon&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;重启 Mihon，再次进入浏览 &amp;gt; 图源 &amp;gt;komga，长按漫画即可加入书架。&lt;/p&gt;
&lt;p&gt;我个人推荐使用这种阅读方式而不是网页版，因为Mihon会预加载漫画，也可以缓存到本地，翻页更流畅。&lt;/p&gt;
&lt;h2&gt;看几乎任何漫画&lt;/h2&gt;
&lt;p&gt;如果本地没有多少漫画资源，或者想阅读更多漫画，可以使用 Mihon 的插件功能。如果你的网络比较好，可以在插件页面看见一个很长的列表，就可以下滑找到中文分类，然后随便安装几个喜欢的插件。信任他们后，回到主页搜索栏输入漫画名，点击全局搜索，点击全部结果就可以把在线资源加入书架啦。&lt;/p&gt;
&lt;p&gt;如果连接不顺畅，可以尝试在网上或者码云搜索&lt;code&gt;Tachiyomi插件&lt;/code&gt;​来下载。此外，我准备了截止发稿时间的中文、多语言插件，可以在这里下载：&lt;a href=&quot;http://duke486.pub:5244/&quot;&gt;插件&lt;/a&gt;。但是随着漫画来源的更新，这里的插件可能会过时，所以最好想办法让Mihon能够加载插件仓库。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/Screenshot_20240510_180908_Mihon-20240510180926-jdslt9d.jpg&quot; alt=&quot;Screenshot_20240510_180908_Mihon&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;我会在后期讲解如何把这些网络资源变成自己漫画库的一部分、公网 IP、内网穿透、域名绑定等操作，让你随时随地都能享受私有云的便利或者和朋友分享。&lt;/p&gt;
&lt;h2&gt;给老手的 DockerCompose&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.3&apos;

services:
  komga:
    image: gotson/komga
    volumes:
      - /home/name/comic:/comics  # 映射漫画目录
      - ./config:/config          # Komga配置文件和数据库存储位置
    ports:
      - &quot;25600:8080&quot;              # 将主机的25600端口映射到容器的8080端口
    environment:
      - SPRING_PROFILES_ACTIVE=native
    restart: always
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;‍&lt;/p&gt;
</content:encoded></item><item><title>ACGN爽记-2-设置Alist，安装媒体库</title><link>https://duke486.com/posts/acgn%E7%88%BD%E8%AE%B0-2-%E8%AE%BE%E7%BD%AEalist%E5%AE%89%E8%A3%85%E5%AA%92%E4%BD%93%E5%BA%93/</link><guid isPermaLink="true">https://duke486.com/posts/acgn%E7%88%BD%E8%AE%B0-2-%E8%AE%BE%E7%BD%AEalist%E5%AE%89%E8%A3%85%E5%AA%92%E4%BD%93%E5%BA%93/</guid><description>设置内网静态IP，为Alist添加卷，配置Jellyfin服务器</description><pubDate>Thu, 22 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::warning&lt;br /&gt;
2024年6月起，由于一些原因，Docker镜像服务器的连通性存在问题，请确认您的网络质量。&lt;br /&gt;
:::&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;上帝说“要有光”，于是有了 BT 协议——Duke486&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;书接上文，我们已经成功在一台主机上部署了 1Panel 面板，并且利用它一键安装了 Alist。有了前面的铺垫，本期基本只需要点几下鼠标就能轻松完成。接下来我们就开始配置自己的网盘，影视库，自动下载等功能。&lt;/p&gt;
&lt;h2&gt;本期效果图&lt;/h2&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240221231356-sh3q5gq.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;新番自动下载，并且自动匹配封面、缩略图、海报、剧情、演员等元数据&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240221231749-9g0q519.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;Alist 网盘和离线下载功能，支持普通下载、磁力链、BT 种子&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240221231540-2pxba5l.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;qbit 网页访问，它负责下载新番，也从 Alist 接受下载任务&lt;/p&gt;
&lt;h2&gt;固定内网 IP 地址&lt;/h2&gt;
&lt;p&gt;一般来说，路由器会把我们家里的所有设备放在一个局域网里，并且给每台设备分配一个 IP 地址。IP 在分配时从地址池中选取，并在租约过期后回收，但是为了方便地访问我们的主机，我们不希望它的内网 IP 发生变化。所以在配置之前，需要设置一下静态IP。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240221232956-s0fmcph.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;最简单的方式是在路由器后台设置，以 TPLINK 路由器为例，进入 IP 与 MAC 绑定页面。对着主机点击加号，IP 就被固定下来了。&lt;/p&gt;
&lt;p&gt;小米路由器则是高级-&amp;gt;DHCP。其他路由器找找带着 DHCP，静态，绑定之类词语的选项。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240221233001-ug5tsr4.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;为 Alist 添加对应目录&lt;/h2&gt;
&lt;p&gt;刚配置好的 Alist 是空白的，我们希望能把它当做网盘使用，同时利用它管理下载和视频库。因此需要为它设置三个文件夹——&lt;code&gt;Upload&lt;/code&gt;​ 上传、&lt;code&gt;Download&lt;/code&gt;​ 下载、&lt;code&gt;Series&lt;/code&gt;​ 剧集。&lt;/p&gt;
&lt;p&gt;打开面板，选择左侧的容器，勾选“显示应用商店容器”，对着 Alist 点击更多-&amp;gt; 编辑-&amp;gt; 挂载，在这里设置目录映射关系。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240221233936-q5uoovq.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;它自带的不需要动，我们直接添加三条映射，文件夹名称参考前文三个单词填写，&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;本机：/home/你的主机用户名/文件夹 容器：/mnt/文件夹&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;最后点击确认，面板提示&lt;code&gt;将会重建容器，未持久化数据将丢失&lt;/code&gt;​，确认。不用担心，因为相关配置文件默认映射到主机位置了。之后通过下图快捷入口登录Alist。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/image-20240221235004-d79f4bu.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;点击页面最底部的管理，进入后台设置页面，在存储选项卡里添加文件夹。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240221235912-1f4eoni.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;驱动选择“本机存储”；挂载路径输入&lt;code&gt;/上传的文件&lt;/code&gt;​，其他名称也可以；根文件夹路径输入&lt;code&gt;/mnt/Upload&lt;/code&gt;​保存即可。其他两个文件夹以此类推。&lt;/p&gt;
&lt;p&gt;至此，我们已经完成网盘功能了，在 Alist 主页右下角可以上传文件，右键文件可以下载，点击文件可以直接预览。&lt;/p&gt;
&lt;h2&gt;安装媒体库&lt;/h2&gt;
&lt;p&gt;接下来是媒体库的安装和设置，和 Alist 相比简单很多。直接在 1Panel 应用商店安装 Jellyfin，选上“允许外部端口”，其他的选项都不管即可。&lt;/p&gt;
&lt;p&gt;安装后，仍然需要添加一条映射：本机为&lt;code&gt;/home/你的主机用户名/Series&lt;/code&gt;​，容器为&lt;code&gt;/media/Series&lt;/code&gt;​，保存，然后通过快捷入口访问，即可打开首次使用设置。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240222001532-qlw6g5m.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;首先选择汉语，账号密码随意，点击添加媒体库，类型为节目，名称随意，文件夹如图填写。下面的语言一律选中文，国家选 People 开头的祖国全名。打钩媒体资料储存方式，打钩将媒体图像保存到媒体所在文件夹，其他的不用动，一路下一步。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240222001808-fnb3fpc.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;立即体验&lt;/h2&gt;
&lt;p&gt;我以芙莉莲为例，演示一下从网盘添加视频。我在 Alist 里面选择 Series 文件夹，创建“葬送的芙莉莲”文件夹，放几集进去。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240222002735-ehe19io.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;在 jellyfin 控制台里点一下扫描，来让他寻找番剧并补全缺失的信息。扫描一旦完成，就可以在浏览器、电视、平板等任何地方打开 jellyfin 网址或者 APP 来享受完美观影体验了~&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240222012251-lx55bfr.png&quot; alt=&quot;image&quot; /&gt;&lt;img src=&quot;assets/image-20240222012411-ye3y9df.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;不过由于元数据获取器是国外的，或者我们的片源命名不规范，个别剧集可能出现没有封面的情况，可以搜索“Jellyfin 手动刮削”来解决问题。也可以搜索 Jellyfin 豆瓣等，采用国内数据来一劳永逸，不过这些都是后话了。希望有更多人能体会到本地观影的乐趣！&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240222013917-8ctyxxk.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
</content:encoded></item><item><title>ACGN爽记-1-面板安装</title><link>https://duke486.com/posts/acgn%E7%88%BD%E8%AE%B0-1-%E9%9D%A2%E6%9D%BF%E5%AE%89%E8%A3%85/</link><guid isPermaLink="true">https://duke486.com/posts/acgn%E7%88%BD%E8%AE%B0-1-%E9%9D%A2%E6%9D%BF%E5%AE%89%E8%A3%85/</guid><description>在服务器上准备1Panel面板，安装第一个Docker应用.</description><pubDate>Sun, 18 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::warning
2024年6月起，由于一些原因，Docker镜像服务器的连通性存在问题，请确认您的网络质量。
:::&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“互联网上的东西都不是你的，只有存在硬盘的东西才属于自己。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我在上网时偶然看的这么一句评论，对此深感赞同。多少音乐，小说，影视作品在网上能看的时候我没有珍惜，等到多年后某一天突然回想起来，却翻遍全网也找不到下载的地址了......&lt;/p&gt;
&lt;p&gt;所以，为了让更多人能便捷的&lt;strong&gt;管理&lt;/strong&gt;自己的“收藏品”，更好地体验欣赏作品的乐趣，我决定开一系列入门文章来分享一下自己的经验。&lt;/p&gt;
&lt;p&gt;如果你在寻找一个在线看动画、漫画、小说的网站或 APP，那么本系列文章并不适合你。&lt;/p&gt;
&lt;h2&gt;效果图&lt;/h2&gt;
&lt;p&gt;在系列文章中私人影视库采用 Jellfin，可存放电视剧、电影、番剧、音乐。支持自动补全海报、封面、简介、演员照片等元数据。
​&lt;img src=&quot;assets/image-20240218202750-jry1ost.png&quot; alt=&quot;image&quot; /&gt;​
​&lt;img src=&quot;assets/image-20240218202922-4z2ndc7.png&quot; alt=&quot;image&quot; /&gt;​
漫画库则采用 Komga，支持在线管理和阅读。虽说 tachidesk 可能功能更丰富些，不过鉴于最近的删库风波，不知道它的插件日后的可用性有没有保障遂放弃。
​&lt;img src=&quot;assets/image-20240218204125-yggx7uq.png&quot; alt=&quot;image&quot; /&gt;
​&lt;img src=&quot;assets/image-20240218204158-o2p4kae.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;最后是文件库，也可以叫云盘，采用大名鼎鼎的 Alist。优势就在于配置和使用起来都异常简单，还可以接入国内网盘，接入离线下载，支持常见格式在线预览（妈妈再也不用担心我熬夜下种子了）&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218204435-iehtbp9.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;先决条件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一台充当服务器的主机（可以是电脑，NAS，云服务器等等。但是考虑到随时访问的需求，最好是后两者）&lt;/li&gt;
&lt;li&gt;主机是 x86 架构，内存 2G+&lt;/li&gt;
&lt;li&gt;如果打算随时访问的话，需要公网 IP 或者购买内网穿透&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目前先介绍如何搭建并在内网访问，外网访问后续单开文章说明。&lt;/p&gt;
&lt;h2&gt;准备主机&lt;/h2&gt;
&lt;p&gt;如果你使用 windows 电脑，需要在应用商店下载一个 ubuntu 作为运行环境。后面的教程以 Ubuntu 环境为基础。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218201150-3hlymas-20240218213827-paccrd9.png&quot; alt=&quot;image-20240218201150-3hlymas&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;如果你使用其他设备或者云服务器，请确保操作系统是 Ubuntu 或类似发行版。&lt;/p&gt;
&lt;p&gt;如果你使用群晖，想安装应用只需要在网上搜索 &lt;code&gt;应用名+DockerCompose&lt;/code&gt; ​就能找到方法。&lt;/p&gt;
&lt;p&gt;在这里我是用的是一台赛扬 J1900 处理器的小主机，功耗很低，可以一直开着当服务器用。海鲜市场大概 150 就可以入一个。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218205450-xeyj7xt.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
&lt;h2&gt;安装面板&lt;/h2&gt;
&lt;p&gt;在 Ubuntu 环境下我们面对的是一个黑底白字的终端。但是作为入门教程我们不需要捣鼓这个玩意，我们使用下图的 1Panel 面板提供的界面来操作，难度直线下降。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218205548-7refzqg.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218210347-rop1o34.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;如果你在 Microsoft store 安装 Ubuntu，那么直接打开就能看见终端。
如果你是其他情况，请打开你家路由器的设备管理页，看看你的机器对应的 IP 是什么。然后通过打开网上搜到的任何一款“SSH”软件，输入 IP 和主机账号密码即可看到终端。&lt;/p&gt;
&lt;p&gt;我们使用 1Panel 官方提供的一键安装命令来安装面板&lt;/p&gt;
&lt;p&gt;​&lt;code&gt;curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh &amp;amp;&amp;amp; sudo bash quick_start.sh&lt;/code&gt;​&lt;/p&gt;
&lt;p&gt;输入命令后直接一路回车，碰见设密码的地方设置一下即可安装成功。安装成功后终端里会显示你的访问地址和账号密码，请务必牢记。我们的安装工作现在大功告成了，可以扔掉这个终端了。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218210734-k7t2id3.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;安装第一个服务&lt;/h2&gt;
&lt;p&gt;打开浏览器，输入终端给我们的链接，然后输入面板的账号密码就可以进入易于使用管理页面了。&lt;/p&gt;
&lt;p&gt;如果突然访问不了了，可能是 IP 变动导致的，这个问题以后细说解决方案，现阶段可以通过在终端输入 &lt;code&gt;sudo 1pctl user-info&lt;/code&gt; ​来获得一个新地址。&lt;img src=&quot;assets/image-20240218211012-55rw9qh.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;我们直接来到面板左侧的应用商店按钮，搜索 Alist 来一键安装我们的第一个应用：网盘服务。安装的时候有很多选项我们全都不用看，建议勾选“外部端口访问”。&lt;img src=&quot;assets/image-20240218211647-zyj1bx8.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;接下来切换到已安装的应用，安装一旦完成就会出现一个入口。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218211826-4vpw957.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;点击服务端口，会要求设置地址，只需要填入我们机器所在的 IP 即可，以后都不会再弹出了。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218212203-sreo6u8.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;填写完后再点一下刚才的入口就能进入应用。初始密码位于应用商店 ＞ 已安装 ＞alist＞ 日志中，点击一下日志按钮，找到 &lt;code&gt;Successfully created the admin user and the initial password is:XXXXXXX&lt;/code&gt; ​这里的 XX 就是你的默认密码，默认用户名是 admin。&lt;/p&gt;
&lt;p&gt;随后通过快捷入口进入 Alist 并登录。我们跟随 Alist 的指引设置一下自己的密码就大功告成了。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/image-20240218212319-g2xvu2r.png&quot; alt=&quot;image&quot; /&gt;​&lt;/p&gt;
&lt;h2&gt;无限可能&lt;/h2&gt;
&lt;p&gt;至此，我们已经打通了一键安装服务的道路，从此以后再继续添加影视库、漫画库、博客、网站等等各种服务不过是分分钟的事情。1Panel 面板本质上是代替我们操作主机上的 Docker 容器来实现运行程序，Docker 容器由于他本身的特性，玩法和可拓展性都极高。&lt;/p&gt;
&lt;p&gt;​&lt;img src=&quot;assets/102115356_p0-20240218212749-19f33g1.jpg&quot; alt=&quot;102115356_p0&quot; /&gt;​&lt;/p&gt;
&lt;p&gt;‍&lt;/p&gt;
</content:encoded></item><item><title>你好Astro，你好Fuwari</title><link>https://duke486.com/posts/helloworld/</link><guid isPermaLink="true">https://duke486.com/posts/helloworld/</guid><description>Here is where all begin!  Ciallo～(∠・ω&lt; )⌒☆</description><pubDate>Sat, 17 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本博客自2024年2月17日通过Wordpress搭建，于2024年6月16日迁移至Astro.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;经过精挑细选，最终选择了基于&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;的&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;Fuwari&lt;/a&gt;主题。充满活力的配色方式、优雅的动画、流畅的控件，尤其是二次元的宣传图，让我对他一见钟情！&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;saicaca/fuwari&quot;}&lt;/p&gt;
</content:encoded></item><item><title>请问您今天要来点音乐吗？</title><link>https://duke486.com/posts/%E9%9F%B3%E4%B9%90%E8%A7%86%E9%A2%91/</link><guid isPermaLink="true">https://duke486.com/posts/%E9%9F%B3%E4%B9%90%E8%A7%86%E9%A2%91/</guid><description>与你的日常，就是奇迹</description><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;!--&lt;/p&gt;
&lt;h2&gt;YouTube&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
--&amp;gt;&lt;/p&gt;
&lt;h2&gt;与你的日常，就是奇迹&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe
width=&quot;100%&quot;
height=&quot;460px&quot;
src=&quot;/video-BlueArchive.html&quot;
frameborder=&quot;0&quot;
style=&quot;border-radius: 15px;&quot;
allowfullscreen&amp;gt;
&amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item></channel></rss>