<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>星光下的赶路人</title><link>https://forstyle.cc/zblog/</link><description></description><item><title>ossutil常用操作</title><link>https://forstyle.cc/zblog/post/133.html</link><description>&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/10/20261021102543179254954351441.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;

&lt;p&gt;ossutil阿里云OSS文件操作工具，可以批量操作文件&lt;/p&gt;

&lt;p&gt;下载地址：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://help.aliyun.com/zh/oss/developer-reference/install-ossutil?spm=a2c4g.11186623.help-menu-31815.d_1_2_2_0.46be110290y1nv&quot;&gt;1.0版本&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://help.aliyun.com/zh/oss/developer-reference/ossutil-overview/?spm=a2c4g.11186623.help-menu-31815.d_1_2_1.451b3236RvYuLj&amp;scm=20140722.H_2786110._.OR_help-T_cn~zh-V_1&quot;&gt;2.0版本&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;windows上直接下载解压后进入解压目录通过cmd命令行操作&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;重点差异：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置文件格式不一样（v1：&lt;code&gt;[Credentials]&lt;/code&gt;；v2：&lt;code&gt;[default]&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;参数风格大体接近，但&lt;strong&gt;读取配置、部分高级参数、返回提示、错误码细节有区别&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;v1 配置文件字段：&lt;code&gt;accessKeyID&lt;/code&gt;（大写ID）；v2：&lt;code&gt;accessKeyId&lt;/code&gt;（小写Id）&lt;/li&gt;
&lt;li&gt;v1 是单二进制老版本；v2 是重构新版，子命令体系有微调&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
&lt;p&gt;统一说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-r&lt;/code&gt;：递归&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-u&lt;/code&gt;：增量，跳过大小/修改时间一致的文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--forbid-overwrite true&lt;/code&gt;：禁止覆盖已有文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--config-file &amp;quot;xxx&amp;quot;&lt;/code&gt;：指定配置文件路径&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;一、版本查看&lt;/h2&gt;

&lt;p&gt;v1（1.7.19）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil -v
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil version
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;二、配置相关&lt;/h2&gt;

&lt;h3&gt;v1&lt;/h3&gt;

&lt;p&gt;生成/交互式配置&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil config
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;配置文件内容模板（v1）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Credentials]
accessKeyID=xxx
accessKeySecret=xxx
endpoint=http://xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;指定配置文件执行（你在用的）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil ls oss://bucket --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;v2&lt;/h3&gt;

&lt;p&gt;交互式配置&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil config
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;配置文件模板（v2）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[default]
accessKeyId=xxx
accessKeySecret=xxx
endpoint=http://xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;指定配置文件&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil ls oss://bucket --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;三、列举 bucket / 列举桶内文件&lt;/h2&gt;

&lt;p&gt;v1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;# 列出所有bucket
ossutil ls --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
# 列出桶内文件（递归）
ossutil ls oss://mybucket/ -r --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil ls
ossutil ls oss://mybucket/ -r
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;四、上传文件/文件夹&lt;/h2&gt;

&lt;p&gt;v1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;# 单个文件上传
ossutil cp D:\test.txt oss://mybucket/ --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
# 文件夹递归上传 + 增量
ossutil cp D:\localdir oss://mybucket/dir/ -r -u --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil cp D:\test.txt oss://mybucket/
ossutil cp D:\localdir oss://mybucket/dir/ -r -u
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;五、下载文件/文件夹&lt;/h2&gt;

&lt;p&gt;v1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil cp oss://mybucket/test.txt D:\ --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
ossutil cp oss://mybucket/dir/ D:\localdir/ -r -u --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil cp oss://mybucket/test.txt D:\
ossutil cp oss://mybucket/dir/ D:\localdir/ -r -u
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;六、桶之间互拷（同region，服务端复制，不落地本地）✅&lt;/h2&gt;

&lt;p&gt;v1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;# 全量递归复制，增量，不覆盖
ossutil cp oss://src-bucket/ oss://dst-bucket/ -r -u --forbid-overwrite true --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil cp oss://src-bucket/ oss://dst-bucket/ -r -u --forbid-overwrite true
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ 源末尾必须带 &lt;code&gt;/&lt;/code&gt;，代表桶根目录所有对象&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;七、同步 sync（镜像同步：源删了，目标也删除，谨慎！）&lt;/h2&gt;

&lt;p&gt;v1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil sync oss://src-bucket/ oss://dst-bucket/ --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
ossutil sync D:\local oss://bucket/dir/ --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil sync oss://src-bucket/ oss://dst-bucket/
ossutil sync D:\local oss://bucket/dir/
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;八、删除对象（慎用！）&lt;/h2&gt;

&lt;p&gt;v1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;# 删除单个
ossutil rm oss://mybucket/test.txt --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
# 递归删除目录下全部
ossutil rm oss://mybucket/dir/ -r --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil rm oss://mybucket/test.txt
ossutil rm oss://mybucket/dir/ -r
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;九、创建 / 删除 bucket&lt;/h2&gt;

&lt;p&gt;v1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil mb oss://newbucket --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
ossutil rb oss://newbucket --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil mb oss://newbucket
ossutil rb oss://newbucket
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;十、获取文件元信息&lt;/h2&gt;

&lt;p&gt;v1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil stat oss://mybucket/test.txt --config-file &amp;quot;C:\Users\user14\.ossutilconfig&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;v2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;ossutil stat oss://mybucket/test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;十一、批量命令通用参数（v1、v2 大部分通用）&lt;/h2&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;code&gt;-r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;递归目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;增量模式，跳过相同文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--forbid-overwrite true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;禁止覆盖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--quiet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;静默输出，少打印日志&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--jobs N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置并发数，如 &lt;code&gt;--jobs 10&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;h1&gt;⚠️ 两个版本踩坑重点（非常关键）&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不要混用配置文件&lt;/strong&gt;：v1的配置文件给v2读、v2给v1读，直接报 Credentials 找不到；&lt;/li&gt;
&lt;li&gt;跨 region 桶复制：ossutil 本身不支持服务端复制，会自动下载到本地再上传，速度很慢；&lt;/li&gt;
&lt;li&gt;你这个兼容S3的私有对象存储，&lt;strong&gt;v1 能跑通，v2 不一定能跑通&lt;/strong&gt;（签名逻辑有差异），所以你现在优先固定 v1.7.19；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cp&lt;/code&gt; 是复制（只新增覆盖，不会删目标多余文件）；&lt;code&gt;sync&lt;/code&gt; 是镜像（会删除目标独有的文件），迁移备份优先用 &lt;code&gt;cp -r -u&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
</description><pubDate>Wed, 21 Oct 2026 10:16:32 +0800</pubDate></item><item><title>OpenList 从入门到进阶：Windows 部署、网盘挂载与多用户分享全指南</title><link>https://forstyle.cc/zblog/post/132.html</link><description>&lt;p&gt;在网盘限速、多平台割裂的今天，如何用一个统一入口管理阿里云盘、百度网盘、夸克网盘等资源，成了很多人的刚需。OpenList 作为 Alist 的社区分支，凭借开源、透明、支持 40+ 种存储协议的特性，成为了当前最值得折腾的聚合方案之一。&lt;/p&gt;

&lt;p&gt;这篇文章整理了从下载安装、网盘挂载，到多用户权限、文件分享的完整流程，希望能帮你少走弯路。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;一、OpenList 与 Alist：为什么选它？&lt;/h2&gt;

&lt;p&gt;简单来说，OpenList 是 Alist 的社区“逃生版”。2025 年 Alist 被出售给第三方公司后，社区对其私有 API 和潜在的数据收集行为产生担忧，部分原贡献者创建了 OpenList 分支。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;特性&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Alist（被收购后）&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;OpenList（社区分支）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;维护方&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;不够科技（贵州）公司&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;OpenList Team，社区驱动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;信任度&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;存在争议&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;强调透明，代码可审计&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;核心风险&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;私有 API 可能泄露 Token&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;已清除原作者私有 API 链接&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;授权协议&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;开源&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;AGPL-3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;对于新用户，建议直接选择 OpenList。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;二、Windows 部署：下载与启动&lt;/h2&gt;

&lt;h3&gt;下载哪个版本？&lt;/h3&gt;

&lt;p&gt;在 GitHub Releases &lt;a href=&quot;https://github.com/OpenListTeam/OpenList&quot;&gt;页面&lt;/a&gt;，Windows 用户会看到两个包：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;openlist-windows-amd64.zip&lt;/code&gt;（普通版）&lt;/strong&gt;：功能完整，适合直接运行 &lt;code&gt;openlist.exe&lt;/code&gt; 长期使用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;openlist-windows-amd64-lite.zip&lt;/code&gt;（精简版）&lt;/strong&gt;：体积更小，适合配合 OpenList Desktop 使用，或不需要本地挂载等高级功能。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：如果你想直接在浏览器里管理网盘，下载普通版即可。&lt;/p&gt;

&lt;h3&gt;启动服务&lt;/h3&gt;

&lt;p&gt;解压后，在 &lt;code&gt;openlist.exe&lt;/code&gt; 所在文件夹打开命令行：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;openlist.exe server
`&amp;lt;/pre&amp;gt;

浏览器访问 `http://localhost:5244` 即可。

&amp;gt; ⚠️ 注意：`nohup` 是 Linux 命令，Windows 下直接运行 `openlist.exe server` 即可。如果想后台运行，可以用 `start /b openlist.exe server`，或使用 NSSM 注册为系统服务。

### 首次登录

默认管理员账号是 `admin`，密码随机生成。查看或重置密码：

&amp;lt;pre&amp;gt;`openlist.exe admin              # 查看管理员信息
openlist.exe admin set 新密码   # 强制修改密码
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;三、挂载国内网盘&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/10/20261009131859179152313999859.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;

&lt;h3&gt;阿里云盘（推荐 OAuth2 方式）&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;打开官方 API 页面 &lt;a href=&quot;https://api.oplist.org/#&quot;&gt;。地址&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/10/20261009132531179152353147249.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;

&lt;p&gt;3.  选择“阿里云盘（OAuth2）”，用手机 App 扫码授权。&lt;/p&gt;

&lt;p&gt;4.  复制返回的 &lt;code&gt;refresh_token&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;5.  在 OpenList 后台“存储”→“添加”，驱动选择“阿里云盘（OAuth2）”，粘贴令牌，开启“使用在线 API”。&lt;/p&gt;

&lt;p&gt;6.  挂载路径自定义，如 &lt;code&gt;/aliyun&lt;/code&gt;。&lt;/p&gt;

&lt;h3&gt;百度网盘&lt;/h3&gt;

&lt;p&gt;百度网盘有严格的&lt;strong&gt;应用沙箱&lt;/strong&gt;限制：通过 OpenList 只能看到 &lt;code&gt;/apps/bdpan/&lt;/code&gt; 目录下的文件。挂载后如果发现是空的，先确认文件是否在这个目录里。&lt;/p&gt;

&lt;h3&gt;夸克网盘&lt;/h3&gt;

&lt;p&gt;需要手动获取 Cookie：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;浏览器登录 &lt;code&gt;pan.quark.cn&lt;/code&gt;，按 &lt;code&gt;F12&lt;/code&gt; 打开开发者工具。&lt;/li&gt;
&lt;li&gt;切换到“网络”标签，刷新页面，随意点一个请求。&lt;/li&gt;
&lt;li&gt;复制请求头里的 &lt;code&gt;Cookie&lt;/code&gt; 字段。&lt;/li&gt;
&lt;li&gt;在 OpenList 添加存储时，驱动选“夸克”，粘贴 Cookie，根文件夹 ID 填 &lt;code&gt;0&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ 夸克网盘在线播放较慢，因为数据需要经过 OpenList 本地代理中转。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;四、文件分享&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/10/20261009131938179152317890081.png&quot; alt=&quot;image.png&quot;&gt;&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;WebDAV 共享&lt;/strong&gt;：在用户设置里开启 WebDAV 权限，对方通过 &lt;code&gt;http://你的IP:5244/dav/&lt;/code&gt; 挂载访问。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/10/20261009132201179152332136278.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;

&lt;p&gt;OpenList 的强大在于它把散落在各个平台的网盘聚合成了一个统一入口，再通过多用户和权限系统实现了可控的共享。虽然配置过程有一些门槛，但一旦跑通，日常使用会非常顺畅。&lt;/p&gt;
</description><pubDate>Fri, 09 Oct 2026 13:17:47 +0800</pubDate></item><item><title>Python 实现高德 GCJ-02 转百度 BD-09：MySQL 批量并发处理与断点续跑</title><link>https://forstyle.cc/zblog/post/131.html</link><description>&lt;p&gt;在实际的数据治理、地图数据迁移以及零售户地理信息处理中，经常会遇到不同地图厂商之间的坐标系不一致问题。&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;portrait-img&quot; src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/09/20260929164216179067133661998.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;高德地图使用 &lt;strong&gt;GCJ-02&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;百度地图使用 &lt;strong&gt;BD-09&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;GPS 原始坐标通常为 &lt;strong&gt;WGS-84&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果数据库中的经纬度来自高德地图，而后续业务需要使用百度地图进行展示、空间计算或者区域匹配，就需要先将 &lt;strong&gt;GCJ-02 坐标转换为 BD-09&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;当数据量比较小时，可以直接逐条转换。&lt;/p&gt;

&lt;p&gt;但是如果数据库中有几十万、几百万甚至上千万条坐标数据，逐条处理会非常慢。&lt;/p&gt;

&lt;p&gt;本文介绍一种适合实际生产环境的处理方式：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;MySQL + Python + 多进程并发 + 批量读取 + 批量更新 + 断点续跑&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;一、问题背景&lt;/h2&gt;

&lt;p&gt;假设数据库中存在一张零售户位置表：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE `t_data_cust` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `cus_name` varchar(255) DEFAULT NULL COMMENT &amp;#39;零售户名称&amp;#39;,
  `address` varchar(255) DEFAULT NULL COMMENT &amp;#39;经营地址&amp;#39;,
  `mapx` float DEFAULT NULL COMMENT &amp;#39;经度&amp;#39;,
  `mapy` float DEFAULT NULL COMMENT &amp;#39;纬度&amp;#39;,
  `baidu_longitude` double DEFAULT NULL COMMENT &amp;#39;百度经度&amp;#39;,
  `baidu_latitude` double DEFAULT NULL COMMENT &amp;#39;百度纬度&amp;#39;,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;mapx = 高德经度
mapy = 高德纬度
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;数据类似：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;id       mapx          mapy
1        119.036411    35.091783
2        119.027665    35.096810
3        119.024164    35.094040
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在需要将：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GCJ-02
   ↓
BD-09
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;转换以后保存到：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;baidu_longitude
baidu_latitude
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;二、GCJ-02 和 BD-09 是什么？&lt;/h1&gt;

&lt;p&gt;在中国互联网地图应用中，经常会接触到几个坐标系。&lt;/p&gt;

&lt;h3&gt;WGS-84&lt;/h3&gt;

&lt;p&gt;GPS 常见的全球坐标系。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;119.000000
35.000000
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;GCJ-02&lt;/h3&gt;

&lt;p&gt;高德地图、腾讯地图等国内地图服务常见的坐标系。&lt;/p&gt;

&lt;h3&gt;BD-09&lt;/h3&gt;

&lt;p&gt;百度地图在 GCJ-02 基础上再次进行偏移后的坐标系。&lt;/p&gt;

&lt;p&gt;因此：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;WGS-84
   ↓
GCJ-02
   ↓
BD-09
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;本文只讨论：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GCJ-02 → BD-09
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;三、GCJ-02 转 BD-09 算法&lt;/h1&gt;

&lt;p&gt;GCJ-02 转 BD-09 可以直接使用公开的数学转换公式。&lt;/p&gt;

&lt;p&gt;Python 实现如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import math


def gcj02_to_bd09(lng, lat):
    &amp;quot;&amp;quot;&amp;quot;
    GCJ-02 转 BD-09

    :param lng: GCJ-02 经度
    :param lat: GCJ-02 纬度
    :return: BD-09 经度、纬度
    &amp;quot;&amp;quot;&amp;quot;

    x = lng
    y = lat

    z = math.sqrt(x * x + y * y) + \
        0.00002 * math.sin(y * math.pi * 3000.0 / 180.0)

    theta = math.atan2(y, x) + \
            0.000003 * math.cos(x * math.pi * 3000.0 / 180.0)

    bd_lng = z * math.cos(theta) + 0.0065
    bd_lat = z * math.sin(theta) + 0.006

    return bd_lng, bd_lat
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;测试：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;lng = 119.03641110
lat = 35.09178304

bd_lng, bd_lat = gcj02_to_bd09(lng, lat)

print(bd_lng)
print(bd_lat)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;四、为什么不能直接逐条查询和更新？&lt;/h1&gt;

&lt;p&gt;最简单的代码可能是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;for row in rows:

    lng = row[&amp;quot;mapx&amp;quot;]
    lat = row[&amp;quot;mapy&amp;quot;]

    bd_lng, bd_lat = gcj02_to_bd09(lng, lat)

    cursor.execute(
        &amp;quot;&amp;quot;&amp;quot;
        UPDATE t_data_cust
        SET baidu_longitude = %s,
            baidu_latitude = %s
        WHERE id = %s
        &amp;quot;&amp;quot;&amp;quot;,
        (bd_lng, bd_lat, row[&amp;quot;id&amp;quot;])
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这种方式存在两个明显问题。&lt;/p&gt;

&lt;h2&gt;1. 数据库交互次数太多&lt;/h2&gt;

&lt;p&gt;假设有：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1,000,000 条数据
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;就可能产生：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1,000,000 次 UPDATE
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;数据库压力比较大。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;2. 单线程速度比较慢&lt;/h2&gt;

&lt;p&gt;即使坐标转换本身非常快：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;读取数据库
    ↓
计算
    ↓
UPDATE
    ↓
读取下一条
    ↓
计算
    ↓
UPDATE
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;整个过程是串行的。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;五、优化思路&lt;/h1&gt;

&lt;p&gt;可以把整个处理过程拆成几个阶段：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;MySQL
  │
  │ 批量读取
  ↓
Python
  │
  ├── 进程1
  ├── 进程2
  ├── 进程3
  ├── 进程4
  ├── 进程5
  └── 进程6
  │
  │ GCJ-02 → BD-09
  ↓
批量结果
  │
  ↓
MySQL 批量 UPDATE
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;核心思想是：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;数据库负责数据存储，CPU 多进程负责坐标计算。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;六、为什么使用多进程？&lt;/h1&gt;

&lt;p&gt;Python 中普通的多线程受到 GIL 的影响。&lt;/p&gt;

&lt;p&gt;虽然这个坐标转换计算量不算特别大，但是如果数据达到几十万、几百万条，多进程能够更加充分地利用多个 CPU 核心。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;单进程

CPU
└── Python
    └── 处理所有数据
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;改成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;6 个进程

CPU
├── Process 1
├── Process 2
├── Process 3
├── Process 4
├── Process 5
└── Process 6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;每个进程处理自己的一批数据。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;七、批量并发处理&lt;/h1&gt;

&lt;p&gt;下面给出一个完整示例。&lt;/p&gt;

&lt;p&gt;首先安装依赖：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install mysql-connector-python
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果使用 Conda：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;conda activate 你的环境
python -m pip install mysql-connector-python
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;八、完整代码&lt;/h1&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import math
import mysql.connector

from concurrent.futures import ProcessPoolExecutor
from mysql.connector import Error


# ============================================================
# MySQL 配置
# ============================================================

DB_CONFIG = {
    &amp;quot;host&amp;quot;: &amp;quot;127.0.0.1&amp;quot;,
    &amp;quot;port&amp;quot;: 3306,
    &amp;quot;user&amp;quot;: &amp;quot;root&amp;quot;,
    &amp;quot;password&amp;quot;: &amp;quot;123456&amp;quot;,
    &amp;quot;database&amp;quot;: &amp;quot;test&amp;quot;,
}


# ============================================================
# GCJ-02 → BD-09
# ============================================================

def gcj02_to_bd09(lng, lat):

    x = lng
    y = lat

    z = math.sqrt(x * x + y * y) + \
        0.00002 * math.sin(
            y * math.pi * 3000.0 / 180.0
        )

    theta = math.atan2(y, x) + \
            0.000003 * math.cos(
                x * math.pi * 3000.0 / 180.0
            )

    bd_lng = z * math.cos(theta) + 0.0065
    bd_lat = z * math.sin(theta) + 0.006

    return bd_lng, bd_lat


# ============================================================
# 单个任务
# ============================================================

def process_batch(batch):

    result = []

    for row in batch:

        row_id = row[0]
        lng = row[1]
        lat = row[2]

        try:

            bd_lng, bd_lat = gcj02_to_bd09(
                float(lng),
                float(lat)
            )

            result.append(
                (
                    bd_lng,
                    bd_lat,
                    row_id
                )
            )

        except Exception as e:

            print(
                f&amp;quot;ID={row_id} 转换失败：{e}&amp;quot;
            )

    return result


# ============================================================
# 创建数据库连接
# ============================================================

def get_connection():

    return mysql.connector.connect(
        host=DB_CONFIG[&amp;quot;host&amp;quot;],
        port=DB_CONFIG[&amp;quot;port&amp;quot;],
        user=DB_CONFIG[&amp;quot;user&amp;quot;],
        password=DB_CONFIG[&amp;quot;password&amp;quot;],
        database=DB_CONFIG[&amp;quot;database&amp;quot;],
    )


# ============================================================
# 获取待处理数据
# ============================================================

def get_data():

    conn = None
    cursor = None

    try:

        conn = get_connection()

        cursor = conn.cursor()

        sql = &amp;quot;&amp;quot;&amp;quot;
        SELECT
            id,
            mapx,
            mapy
        FROM t_data_cust
        WHERE mapx IS NOT NULL
          AND mapy IS NOT NULL
          AND baidu_longitude IS NULL
          AND baidu_latitude IS NULL
        &amp;quot;&amp;quot;&amp;quot;

        cursor.execute(sql)

        rows = cursor.fetchall()

        print(f&amp;quot;待处理数据：{len(rows)} 条&amp;quot;)

        return rows

    finally:

        if cursor:
            cursor.close()

        if conn:
            conn.close()


# ============================================================
# 批量更新数据库
# ============================================================

def update_database(results):

    if not results:
        return

    conn = None
    cursor = None

    try:

        conn = get_connection()

        cursor = conn.cursor()

        sql = &amp;quot;&amp;quot;&amp;quot;
        UPDATE t_data_cust
        SET
            baidu_longitude = %s,
            baidu_latitude = %s
        WHERE id = %s
        &amp;quot;&amp;quot;&amp;quot;

        cursor.executemany(
            sql,
            results
        )

        conn.commit()

        print(
            f&amp;quot;数据库更新完成：{len(results)} 条&amp;quot;
        )

    except Exception:

        if conn:
            conn.rollback()

        raise

    finally:

        if cursor:
            cursor.close()

        if conn:
            conn.close()


# ============================================================
# 主程序
# ============================================================

def main():

    print(&amp;quot;=&amp;quot; * 60)
    print(&amp;quot;GCJ-02 → BD-09 批量并发转换&amp;quot;)
    print(&amp;quot;=&amp;quot; * 60)

    # 读取数据
    rows = get_data()

    if not rows:

        print(&amp;quot;没有需要处理的数据&amp;quot;)

        return

    # ========================================================
    # 分批
    # ========================================================

    batch_size = 5000

    batches = []

    for i in range(
        0,
        len(rows),
        batch_size
    ):

        batch = rows[
            i:i + batch_size
        ]

        batches.append(batch)

    print(
        f&amp;quot;数据总量：{len(rows)}&amp;quot;
    )

    print(
        f&amp;quot;批次数量：{len(batches)}&amp;quot;
    )

    # ========================================================
    # 并发处理
    # ========================================================

    max_workers = 6

    print(
        f&amp;quot;并发进程数：{max_workers}&amp;quot;
    )

    all_results = []

    with ProcessPoolExecutor(
        max_workers=max_workers
    ) as executor:

        results = executor.map(
            process_batch,
            batches
        )

        for result in results:

            all_results.extend(result)

            print(
                f&amp;quot;当前已计算：{len(all_results)} 条&amp;quot;
            )

    print(
        f&amp;quot;坐标转换完成：{len(all_results)} 条&amp;quot;
    )

    # ========================================================
    # 批量更新数据库
    # ========================================================

    update_database(
        all_results
    )

    print(&amp;quot;=&amp;quot; * 60)
    print(&amp;quot;全部处理完成&amp;quot;)
    print(&amp;quot;=&amp;quot; * 60)


if __name__ == &amp;quot;__main__&amp;quot;:

    main()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;九、为什么这里使用 5000 条作为一个批次？&lt;/h1&gt;

&lt;p&gt;代码中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;batch_size = 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;假设有：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;100000 条数据
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;那么会被拆成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;5000
5000
5000
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;总共：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;20 个任务
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后交给：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;ProcessPoolExecutor(max_workers=6)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;处理。&lt;/p&gt;

&lt;p&gt;实际效果类似：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;批次1 ──→ 进程1
批次2 ──→ 进程2
批次3 ──→ 进程3
批次4 ──→ 进程4
批次5 ──→ 进程5
批次6 ──→ 进程6

批次7 ──→ 空闲进程
批次8 ──→ 空闲进程
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十、为什么不建议一次创建几十万个任务？&lt;/h1&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;executor.submit(
    gcj02_to_bd09,
    lng,
    lat
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后创建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1000000 个 Future
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这种方式会增加：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 内存占用&lt;/li&gt;
&lt;li&gt;Future 对象数量&lt;/li&gt;
&lt;li&gt;进程通信开销&lt;/li&gt;
&lt;li&gt;调度开销&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此更适合：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;批量提交任务，而不是一条数据一个任务。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十一、六并发应该如何设置？&lt;/h1&gt;

&lt;p&gt;如果机器 CPU 比较强，可以：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;max_workers = 6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;6 个 CPU 工作进程
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果服务器是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;8 核
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以测试：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;max_workers = 6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;或者：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;max_workers = 8
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是并不是进程越多越快。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4进程 → 100秒
6进程 → 75秒
8进程 → 72秒
12进程 → 78秒
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可能出现这种情况。&lt;/p&gt;

&lt;p&gt;原因是进程之间存在：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CPU 调度
进程通信
内存
数据库
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;等额外开销。&lt;/p&gt;

&lt;p&gt;因此建议实际测试。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十二、更加重要：支持断点续跑&lt;/h1&gt;

&lt;p&gt;实际生产环境中，数据量比较大的时候，程序不一定一次就能跑完。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;总数据：4,700,000

第一次：
处理 1,500,000
程序异常退出

第二次：
继续处理剩余数据
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这时候：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;WHERE baidu_longitude IS NULL
  AND baidu_latitude IS NULL
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;就非常重要。&lt;/p&gt;

&lt;p&gt;因为已经处理完成的数据：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;baidu_longitude IS NOT NULL
baidu_latitude IS NOT NULL
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下一次不会再次处理。&lt;/p&gt;

&lt;p&gt;也就是说：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;第一次运行

100万
 ↓
转换
 ↓
写入百度坐标


第二次运行

查询百度坐标为空的数据
 ↓
继续处理
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这实际上就是一种非常简单的：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;断点续跑机制。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十三、处理过程中程序中断怎么办？&lt;/h1&gt;

&lt;p&gt;假设：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;总数据：1,000,000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;程序运行到：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;600,000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;突然：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;电脑重启
网络断开
程序异常
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果前面的数据已经提交到数据库：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;baidu_longitude
baidu_latitude
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;已经存在。&lt;/p&gt;

&lt;p&gt;重新运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;WHERE baidu_longitude IS NULL
  AND baidu_latitude IS NULL
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;那么只会获取剩余数据。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;第一次

1,000,000
 ↓
600,000 已完成


第二次

400,000
 ↓
继续处理
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此不需要重新计算全部数据。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十四、不要把所有结果一直放在内存中&lt;/h1&gt;

&lt;p&gt;上面的示例为了方便理解：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;all_results = []
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后把所有结果放到内存。&lt;/p&gt;

&lt;p&gt;如果只有：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4万条
10万条
几十万条
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;问题一般不大。&lt;/p&gt;

&lt;p&gt;但是如果是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;500万
1000万
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;就不建议这样做。&lt;/p&gt;

&lt;p&gt;更加适合：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;读取一批
 ↓
并发计算
 ↓
立即写数据库
 ↓
释放内存
 ↓
读取下一批
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也就是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;MySQL
 ↓
50000
 ↓
并发计算
 ↓
UPDATE
 ↓
50000
 ↓
并发计算
 ↓
UPDATE
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样内存更加稳定。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十五、百万级数据推荐的架构&lt;/h1&gt;

&lt;p&gt;如果数据量达到百万级甚至千万级，可以进一步改成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                  MySQL
                    │
                    ↓
              分批读取 50000
                    │
                    ↓
        ┌─────────────────────┐
        │  ProcessPoolExecutor│
        │                     │
        │ P1  P2  P3  P4     │
        │ P5  P6              │
        └─────────────────────┘
                    │
                    ↓
              转换结果
                    │
                    ↓
             executemany()
                    │
                    ↓
                  MySQL
                    │
                    ↓
              下一批数据
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样不会因为一次加载全部数据导致内存暴涨。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十六、一个非常重要的问题：坐标系必须一致&lt;/h1&gt;

&lt;p&gt;做空间计算时，这一点非常重要。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;市场区域边界
    ↓
BD-09

零售户
    ↓
GCJ-02
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;那么不能直接：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;Point(mapx, mapy)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后和 BD-09 Polygon 判断。&lt;/p&gt;

&lt;p&gt;因为两个坐标系不是同一个坐标系。&lt;/p&gt;

&lt;p&gt;正确方式应该是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;零售户 GCJ-02
      │
      ↓
GCJ-02 → BD-09
      │
      ↓
零售户 BD-09
      │
      ↓
与市场区域 BD-09 Polygon 计算
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;bd_lng, bd_lat = gcj02_to_bd09(
    mapx,
    mapy
)

point = Point(
    bd_lng,
    bd_lat
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后再进行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;polygon.covers(point)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十七、为什么推荐 &lt;code&gt;covers()&lt;/code&gt; 而不是 &lt;code&gt;contains()&lt;/code&gt;？&lt;/h1&gt;

&lt;p&gt;如果进行市场区域空间匹配：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;polygon.contains(point)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有一个问题。&lt;/p&gt;

&lt;p&gt;如果零售户刚好位于：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Polygon 边界线上
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;那么：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;contains()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可能返回：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;False
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;polygon.covers(point)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;会把边界上的点也计算进去。&lt;/p&gt;

&lt;p&gt;对于“一个零售户属于哪个市场区域”这种业务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;polygon.covers(point)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通常更加符合业务需求。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十八、为什么需要注意边界点？&lt;/h1&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;       A────────B
      /          \
     /            \
    C      ●       D
     \            /
      \          /
       E────────F
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;● = 零售户
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;那么可以判断：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;polygon.covers(point)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果零售户刚好落在：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A-B
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这条边界线上，也可以统计到该区域。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十九、后续可以进一步使用 STRtree 加速&lt;/h1&gt;

&lt;p&gt;如果不仅需要坐标转换，还需要：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;市场区域 Polygon
+
几十万个零售户
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;进行空间匹配。&lt;/p&gt;

&lt;p&gt;不要让每个零售户遍历全部 Polygon：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;for customer in customers:

    for polygon in polygons:

        if polygon.covers(customer):
            ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;假设：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;100000 个零售户
10000 个区域
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;理论上可能产生：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;100000 × 10000
=
10亿次判断
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这会非常慢。&lt;/p&gt;

&lt;p&gt;可以使用 Shapely 的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;STRtree
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;建立空间索引：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from shapely.strtree import STRtree

tree = STRtree(polygons)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;candidate_indexes = tree.query(point)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;先找出空间上可能相交的 Polygon，再进行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;polygon.covers(point)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样可以显著减少实际判断次数。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;二十、一个完整的数据处理架构&lt;/h1&gt;

&lt;p&gt;最终可以形成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                MySQL
                  │
        ┌─────────┴─────────┐
        │                   │
        ↓                   ↓
  零售户坐标             区域边界
  GCJ-02                  BD-09
        │                   │
        ↓                   │
 GCJ-02 → BD-09             │
        │                   │
        ↓                   ↓
   BD-09 Point        BD-09 Polygon
        │                   │
        └─────────┬─────────┘
                  ↓
              STRtree
                  │
                  ↓
            空间匹配
                  │
                  ↓
          统计每个区域户数
                  │
                  ↓
       t_comm_area_info.now_num
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;二十一、生产环境建议&lt;/h1&gt;

&lt;p&gt;如果数据量比较大，建议遵循以下原则。&lt;/p&gt;

&lt;h3&gt;1. 不要一次读取全部数据&lt;/h3&gt;

&lt;p&gt;使用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;5000
10000
50000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;等批次。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h3&gt;2. 并发数不要盲目提高&lt;/h3&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;max_workers = 6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;先测试。&lt;/p&gt;

&lt;p&gt;根据 CPU、内存和实际耗时再调整。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h3&gt;3. 数据库更新使用 executemany&lt;/h3&gt;

&lt;p&gt;不要：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;UPDATE
UPDATE
UPDATE
UPDATE
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;cursor.executemany(
    sql,
    results
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;减少数据库交互。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h3&gt;4. 增加断点续跑条件&lt;/h3&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;WHERE baidu_longitude IS NULL
  AND baidu_latitude IS NULL
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h3&gt;5. 转换结果建议使用 DOUBLE&lt;/h3&gt;

&lt;p&gt;不要使用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;FLOAT
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;保存高精度经纬度时建议：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;DOUBLE
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;baidu_longitude DOUBLE DEFAULT NULL,
baidu_latitude DOUBLE DEFAULT NULL
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;二十二、最终效果&lt;/h1&gt;

&lt;p&gt;经过优化以后，整体处理流程从：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;查询一条
 ↓
转换一条
 ↓
更新一条
 ↓
查询一条
 ↓
转换一条
 ↓
更新一条
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;优化成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;批量查询
    ↓
    ↓
┌───────────────┐
│ 6进程并发计算 │
└───────────────┘
    ↓
批量结果
    ↓
executemany
    ↓
MySQL
    ↓
下一批
&lt;/code&gt;&lt;/pre&gt;

&lt;p&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;/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;多进程&lt;/td&gt;
&lt;td&gt;提高 CPU 利用率&lt;/td&gt;
&lt;/tr&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;executemany&lt;/td&gt;
&lt;td&gt;减少 UPDATE 次数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NULL 判断&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;保存更高精度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GCJ-02 → BD-09&lt;/td&gt;
&lt;td&gt;保证坐标系一致&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STRtree&lt;/td&gt;
&lt;td&gt;加速大量空间匹配&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;covers()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将边界上的点纳入统计&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;对于几十万到几百万条坐标数据，这种方式比单条查询、单条计算、单条 UPDATE 更适合实际的数据处理任务。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;总结&lt;/h2&gt;

&lt;p&gt;高德坐标转换成百度坐标，本身并不复杂，真正影响大数据处理效率的主要是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;数据库读取方式
        +
任务拆分方式
        +
并发模型
        +
数据库写入方式
        +
断点续跑
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此推荐采用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;MySQL
  ↓
批量读取
  ↓
ProcessPoolExecutor
  ↓
6进程并发
  ↓
GCJ-02 → BD-09
  ↓
批量 UPDATE
  ↓
继续下一批
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果后续还需要将转换后的百度坐标用于&lt;strong&gt;市场网格、村级区域、许可证布局等空间区域匹配&lt;/strong&gt;，可以继续结合 &lt;code&gt;Shapely + STRtree&lt;/code&gt;，实现：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;零售户坐标
    ↓
百度坐标
    ↓
判断所在区域 Polygon
    ↓
统计每个村/网格零售户数量
    ↓
更新 t_comm_area_info.now_num
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样就可以从单纯的“坐标转换程序”，进一步扩展成完整的&lt;strong&gt;零售户空间数据治理和市场网格分析程序&lt;/strong&gt;。&lt;/p&gt;
</description><pubDate>Tue, 29 Sep 2026 16:40:11 +0800</pubDate></item><item><title>DolphinScheduler 3.2.2 集成 SeaTunnel 任务启动失败问题排查与解决方案</title><link>https://forstyle.cc/zblog/post/130.html</link><description>&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/09/20260929090854179064413483957.png&quot; alt=&quot;9129263df661e42ce1a673a55d1c1148.png&quot;&gt;&lt;/p&gt;

&lt;h2&gt;一、问题背景&lt;/h2&gt;

&lt;p&gt;在使用 &lt;strong&gt;Apache DolphinScheduler 3.2.2&lt;/strong&gt; 调度 &lt;strong&gt;SeaTunnel&lt;/strong&gt; 数据同步任务时，发现任务在 DolphinScheduler 中执行失败。&lt;/p&gt;

&lt;p&gt;SeaTunnel 本身安装正常，手动在服务器上执行 SeaTunnel 命令也能够正常运行，但是通过 DolphinScheduler 的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash ./bin/start-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;启动集群后，再执行 SeaTunnel 任务会出现以下错误：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;18_132.sh: 行 4: /bin/seatunnel.sh: 没有那个文件或目录
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;经过排查发现：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;手动启动 Worker 时，SeaTunnel 任务可以正常执行；&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;start-all.sh&lt;/code&gt; 启动 DolphinScheduler 后，SeaTunnel 任务执行失败；&lt;/li&gt;
&lt;li&gt;SeaTunnel 安装目录实际存在；&lt;/li&gt;
&lt;li&gt;DolphinScheduler Worker 的 &lt;code&gt;SEATUNNEL_HOME&lt;/code&gt; 环境变量在不同启动方式下存在差异。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最终定位到问题根因：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DolphinScheduler 的 &lt;code&gt;dolphinscheduler-daemon.sh&lt;/code&gt; 在启动 Worker 时，会使用 &lt;code&gt;bin/env/dolphinscheduler_env.sh&lt;/code&gt; 覆盖 Worker 自己的 &lt;code&gt;conf/dolphinscheduler_env.sh&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;因此，仅修改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;并不能保证通过 &lt;code&gt;start-all.sh&lt;/code&gt; 启动后仍然生效。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;二、环境信息&lt;/h1&gt;

&lt;p&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;DolphinScheduler&lt;/td&gt;
&lt;td&gt;3.2.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SeaTunnel&lt;/td&gt;
&lt;td&gt;本地部署&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;操作系统&lt;/td&gt;
&lt;td&gt;Linux / Ubuntu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;OpenJDK 17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DolphinScheduler目录&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/usr/local/server/dolphinscheduler&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SeaTunnel目录&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/usr/local/server/seatunnel&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker目录&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/usr/local/server/dolphinscheduler/worker-server&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker端口&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1234&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;SeaTunnel 实际安装位置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;正常情况下需要：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样 DolphinScheduler 执行 SeaTunnel 时：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;${SEATUNNEL_HOME}/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;才能正确解析为：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/server/seatunnel/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;三、故障现象&lt;/h1&gt;

&lt;p&gt;DolphinScheduler 执行 SeaTunnel 任务时，日志出现：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;18_132.sh: 行 4: /bin/seatunnel.sh: 没有那个文件或目录
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;从任务执行命令来看：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;${SEATUNNEL_HOME}/bin/seatunnel.sh \
    --config /tmp/dolphinscheduler/exec/process/default/185039101090048/185039137408256_2/18/132/seatunnel_18_132.conf \
    --deploy-mode cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;正常情况下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;${SEATUNNEL_HOME}
        ↓
/usr/local/server/seatunnel
        ↓
/usr/local/server/seatunnel/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是实际错误变成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;${SEATUNNEL_HOME}
        ↓
空
        ↓
/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此可以初步判断：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;问题不是 SeaTunnel 的 &lt;code&gt;seatunnel.sh&lt;/code&gt; 不存在，而是 DolphinScheduler Worker 执行任务时没有获得 &lt;code&gt;SEATUNNEL_HOME&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;四、首先确认 SeaTunnel 本身是否正常&lt;/h1&gt;

&lt;p&gt;先检查 SeaTunnel：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls -l /usr/local/server/seatunnel/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果能够看到：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/usr/local/server/seatunnel/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;说明 SeaTunnel 文件本身存在。&lt;/p&gt;

&lt;p&gt;然后测试：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/server/seatunnel/bin/seatunnel.sh --help
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果能够正常输出 SeaTunnel 帮助信息，则说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SeaTunnel 安装正常；&lt;/li&gt;
&lt;li&gt;Java 基础环境基本正常；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;seatunnel.sh&lt;/code&gt; 本身可以执行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;进一步测试环境变量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export SEATUNNEL_HOME=/usr/local/server/seatunnel

$SEATUNNEL_HOME/bin/seatunnel.sh --help
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果正常，则进一步证明问题集中在：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;DolphinScheduler Worker → 环境变量
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而不是 SeaTunnel 本身。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;五、第一次排查：修改 Worker 环境文件&lt;/h1&gt;

&lt;p&gt;DolphinScheduler Worker 使用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/usr/local/server/dolphinscheduler/worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此最初的解决方法是在该文件中增加：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -n &amp;quot;SEATUNNEL_HOME&amp;quot; \
/usr/local/server/dolphinscheduler/worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果存在：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;export SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再手动执行 Worker：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/server/dolphinscheduler/worker-server/bin/start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;此时 SeaTunnel 任务可以正常运行。&lt;/p&gt;

&lt;p&gt;这说明：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;worker-server/conf/dolphinscheduler_env.sh&lt;/code&gt; 中增加 &lt;code&gt;SEATUNNEL_HOME&lt;/code&gt; 是有效的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但是问题并没有彻底解决。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;六、为什么手动启动可以，start-all.sh 却不行？&lt;/h1&gt;

&lt;p&gt;这是本次问题最关键的地方。&lt;/p&gt;

&lt;p&gt;手动启动：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/server/dolphinscheduler/worker-server/bin/start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;与：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash ./bin/start-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;实际上不是完全相同的启动路径。&lt;/p&gt;

&lt;h2&gt;6.1 手动启动路径&lt;/h2&gt;

&lt;p&gt;手动启动大致为：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;worker-server/bin/start.sh
        ↓
读取
worker-server/conf/dolphinscheduler_env.sh
        ↓
读取 SEATUNNEL_HOME
        ↓
启动 Worker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此手动启动时：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;能够正常生效。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;七、start-all.sh 的实际启动流程&lt;/h1&gt;

&lt;p&gt;检查：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/server/dolphinscheduler/bin/start-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以发现 Worker 并不是直接调用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;worker-server/bin/start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash bin/dolphinscheduler-daemon.sh start worker-server
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也就是说：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;start-all.sh
    ↓
dolphinscheduler-daemon.sh
    ↓
worker-server/bin/start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;真正关键的是中间这一层：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;dolphinscheduler-daemon.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;八、发现真正的根因：环境文件会被覆盖&lt;/h1&gt;

&lt;p&gt;查看：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat /usr/local/server/dolphinscheduler/bin/dolphinscheduler-daemon.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中存在：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;function overwrite_server_env() {
  local server=$1
  local server_env_file=&amp;quot;${DOLPHINSCHEDULER_HOME}/${server}/conf/dolphinscheduler_env.sh&amp;quot;

  if [ -f &amp;quot;${BIN_ENV_FILE}&amp;quot; ]; then
    echo &amp;quot;Overwrite ${server}/conf/dolphinscheduler_env.sh using bin/env/dolphinscheduler_env.sh.&amp;quot;
    cp &amp;quot;${BIN_ENV_FILE}&amp;quot; &amp;quot;${server_env_file}&amp;quot;
  else
    echo &amp;quot;Start server ${server} using env config path ${server_env_file}, because file ${BIN_ENV_FILE} not exists.&amp;quot;
  fi
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;BIN_ENV_FILE=&amp;quot;${DOLPHINSCHEDULER_HOME}/bin/env/dolphinscheduler_env.sh&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而 Worker 启动时执行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;overwrite_server_env &amp;quot;${command}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此实际发生的是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/usr/local/server/dolphinscheduler/bin/env/dolphinscheduler_env.sh
                ↓
                ↓ cp
                ↓
/usr/local/server/dolphinscheduler/worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也就是说：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;worker-server/conf/dolphinscheduler_env.sh&lt;/code&gt; 并不是最终的配置源。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;它会在 DolphinScheduler 使用 &lt;code&gt;dolphinscheduler-daemon.sh&lt;/code&gt; 启动 Worker 时被覆盖。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;九、完整问题链路&lt;/h1&gt;

&lt;p&gt;整个问题可以表示为：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                    DolphinScheduler
                           │
                           ▼
                    start-all.sh
                           │
                           ▼
              dolphinscheduler-daemon.sh
                           │
                           ▼
                 overwrite_server_env()
                           │
                           │
              ┌────────────┴────────────┐
              │                         │
              ▼                         ▼
bin/env/dolphinscheduler_env.sh    worker-server/conf/
                                  dolphinscheduler_env.sh
              │                         ▲
              │                         │
              └────────── cp ──────────┘
                                        │
                                        ▼
                              worker-server/bin/start.sh
                                        │
                                        ▼
                              读取 dolphinscheduler_env.sh
                                        │
                                        ▼
                              SEATUNNEL_HOME 是否存在？
                                        │
                         ┌──────────────┴──────────────┐
                         │                             │
                        是                             否
                         │                             │
                         ▼                             ▼
              /usr/local/server/             ${SEATUNNEL_HOME}
                  seatunnel                  为空
                         │                             │
                         ▼                             ▼
           /usr/local/server/seatunnel/       /bin/seatunnel.sh
               bin/seatunnel.sh                     │
                                                     ▼
                                                  ❌ 失败
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十、正确解决方案&lt;/h1&gt;

&lt;h2&gt;10.1 不应该只修改 Worker 配置文件&lt;/h2&gt;

&lt;p&gt;之前修改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/server/dolphinscheduler/worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;虽然可以让手动启动生效，但是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;start-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;启动时会重新覆盖该文件。&lt;/p&gt;

&lt;p&gt;因此不推荐只修改这里。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;10.2 修改统一环境配置&lt;/h2&gt;

&lt;p&gt;正确配置文件应该是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/usr/local/server/dolphinscheduler/bin/env/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;首先检查：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -n &amp;quot;SEATUNNEL_HOME&amp;quot; \
/usr/local/server/dolphinscheduler/bin/env/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果没有结果，则增加：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &amp;#39;export SEATUNNEL_HOME=/usr/local/server/seatunnel&amp;#39; &amp;gt;&amp;gt; \
/usr/local/server/dolphinscheduler/bin/env/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后确认：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tail -n 10 \
/usr/local/server/dolphinscheduler/bin/env/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;应该能够看到：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十一、重新启动 DolphinScheduler&lt;/h1&gt;

&lt;p&gt;修改统一配置之后，需要重新启动服务。&lt;/p&gt;

&lt;p&gt;进入：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /usr/local/server/dolphinscheduler
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;停止：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash ./bin/stop-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查 Worker：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ps -ef | grep WorkerServer | grep -v grep
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;确认旧 Worker 已经停止后，再执行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash ./bin/start-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十二、验证配置是否被正确复制&lt;/h1&gt;

&lt;p&gt;启动完成以后，首先检查：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -n &amp;quot;SEATUNNEL_HOME&amp;quot; \
/usr/local/server/dolphinscheduler/worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;应该看到：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;export SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是因为：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;bin/env/dolphinscheduler_env.sh
        ↓
dolphinscheduler-daemon.sh
        ↓
overwrite_server_env()
        ↓
worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;已经完成了正确的配置复制。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十三、进一步检查 Worker 进程环境&lt;/h1&gt;

&lt;p&gt;仅检查配置文件还不够，最好检查&lt;strong&gt;实际运行中的 Worker JVM 环境&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;先找到 Worker PID：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ps -ef | grep WorkerServer | grep -v grep
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;例如得到：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;dolphin+ 153245 ... org.apache.dolphinscheduler.server.worker.WorkerServer
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;PID=153245

cat /proc/$PID/environ | tr &amp;#39;\0&amp;#39; &amp;#39;\n&amp;#39; | \
grep -E &amp;#39;SEATUNNEL_HOME|DOLPHINSCHEDULER_HOME|JAVA_HOME&amp;#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;正常应该至少包含：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样才能证明：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;SEATUNNEL_HOME&lt;/code&gt; 不仅写入了配置文件，而且真正进入了 Worker 进程环境。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十四、最终验证 SeaTunnel 任务&lt;/h1&gt;

&lt;p&gt;重新执行 DolphinScheduler 中的 SeaTunnel 任务。&lt;/p&gt;

&lt;p&gt;任务命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;${SEATUNNEL_HOME}/bin/seatunnel.sh \
--config /tmp/dolphinscheduler/exec/process/default/.../seatunnel_18_132.conf \
--deploy-mode cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;此时应该被正确解析为：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/usr/local/server/seatunnel/bin/seatunnel.sh \
--config /tmp/dolphinscheduler/exec/process/default/.../seatunnel_18_132.conf \
--deploy-mode cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不再出现：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/bin/seatunnel.sh: 没有那个文件或目录
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十五、建议增加一项启动检查&lt;/h1&gt;

&lt;p&gt;为了以后避免类似问题，可以在部署完成后执行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &amp;quot;===== DolphinScheduler Environment =====&amp;quot;

echo &amp;quot;DOLPHINSCHEDULER_HOME=$DOLPHINSCHEDULER_HOME&amp;quot;
echo &amp;quot;SEATUNNEL_HOME=$SEATUNNEL_HOME&amp;quot;
echo &amp;quot;JAVA_HOME=$JAVA_HOME&amp;quot;

echo &amp;quot;===== SeaTunnel =====&amp;quot;

ls -l &amp;quot;$SEATUNNEL_HOME/bin/seatunnel.sh&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;SEATUNNEL_HOME=
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;或者：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ls: cannot access .../seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;就不要继续执行任务，应先修复环境变量。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十六、另一个独立问题：Java 17 的 InaccessibleObjectException&lt;/h1&gt;

&lt;p&gt;在排查过程中还发现了另外一个异常：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;java.lang.reflect.InaccessibleObjectException:
Unable to make field private final int java.lang.ProcessImpl.pid accessible:
module java.base does not &amp;quot;opens java.lang&amp;quot; to unnamed module
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个问题与：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/bin/seatunnel.sh: 没有那个文件或目录
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;不是同一个问题。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;前者属于：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;DolphinScheduler
        ↓
Java 17
        ↓
反射访问 JDK 内部字段
        ↓
Java Module System
        ↓
InaccessibleObjectException
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;后者属于：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;DolphinScheduler Worker
        ↓
SEATUNNEL_HOME
        ↓
环境变量没有传递
        ↓
/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此排查时应该分开处理。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十七、Java 17 问题的处理方向&lt;/h1&gt;

&lt;p&gt;当前 Worker 使用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Java 17
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而 DolphinScheduler 相关代码中存在：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;java.lang.ProcessImpl.pid
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;等反射访问。&lt;/p&gt;

&lt;p&gt;后续可以检查 Worker 的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/usr/local/server/dolphinscheduler/worker-server/bin/jvm_args_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;以及实际启动参数：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ps -ef | grep WorkerServer | grep -v grep
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;重点确认是否需要增加类似：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;--add-opens=java.base/java.lang=ALL-UNNAMED
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对于另外出现的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;java.lang.invoke.SerializedLambda.capturingClass
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;则可能涉及：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;--add-opens=java.base/java.lang.invoke=ALL-UNNAMED
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不过这属于&lt;strong&gt;第二阶段问题&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;建议先确认 SeaTunnel 调度链路恢复正常，再单独处理 Java 17 反射兼容问题。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十八、排查过程中最容易犯的错误&lt;/h1&gt;

&lt;h2&gt;错误一：只修改 worker-server 配置&lt;/h2&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vim /usr/local/server/dolphinscheduler/worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;增加：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后认为问题已经解决。&lt;/p&gt;

&lt;p&gt;实际上：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;start-all.sh
    ↓
dolphinscheduler-daemon.sh
    ↓
overwrite_server_env()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;会覆盖这个文件。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;错误二：只测试手动启动&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;worker-server/bin/start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;正常并不代表：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;start-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;正常。&lt;/p&gt;

&lt;p&gt;因为两种启动路径不同。&lt;/p&gt;

&lt;p&gt;因此排查 DolphinScheduler 服务环境问题时，应优先验证&lt;strong&gt;实际生产启动方式&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h2&gt;错误三：看到 &lt;code&gt;/bin/seatunnel.sh&lt;/code&gt; 就认为 SeaTunnel 没安装&lt;/h2&gt;

&lt;p&gt;实际上：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个路径本身就暴露了问题。&lt;/p&gt;

&lt;p&gt;如果正确配置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最终路径应该是：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/usr/local/server/seatunnel/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/bin/seatunnel.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;实际上说明：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;${SEATUNNEL_HOME}&lt;/code&gt; 没有展开出有效值。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;十九、推荐的最终配置结构&lt;/h1&gt;

&lt;p&gt;建议把 DolphinScheduler 与 SeaTunnel 的环境配置统一管理：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/usr/local/server/dolphinscheduler/
│
├── bin/
│   ├── start-all.sh
│   ├── stop-all.sh
│   ├── dolphinscheduler-daemon.sh
│   │
│   └── env/
│       └── dolphinscheduler_env.sh    ← ★统一配置源
│
├── worker-server/
│   ├── bin/
│   │   └── start.sh
│   │
│   └── conf/
│       └── dolphinscheduler_env.sh    ← daemon启动时覆盖
│
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;统一配置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# SeaTunnel
export SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;start-all.sh
       ↓
dolphinscheduler-daemon.sh
       ↓
bin/env/dolphinscheduler_env.sh
       ↓
worker-server/conf/dolphinscheduler_env.sh
       ↓
worker-server/bin/start.sh
       ↓
Worker JVM
       ↓
SeaTunnel Task
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;整个链路的环境变量才能保持一致。&lt;/p&gt;

&lt;p&gt;&lt;hr&gt;&lt;/p&gt;

&lt;h1&gt;二十、总结&lt;/h1&gt;

&lt;p&gt;本次问题最终可以归纳为：&lt;/p&gt;

&lt;h3&gt;表面错误&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/bin/seatunnel.sh: 没有那个文件或目录
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;直接原因&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;SEATUNNEL_HOME 为空
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;深层原因&lt;/h3&gt;

&lt;p&gt;通过：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;start-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;启动 DolphinScheduler 时：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dolphinscheduler-daemon.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;会执行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;overwrite_server_env &amp;quot;${command}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;将：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;bin/env/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;复制覆盖：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而 &lt;code&gt;SEATUNNEL_HOME&lt;/code&gt; 之前只配置在 Worker 自己的配置文件中，所以被覆盖后丢失。&lt;/p&gt;

&lt;h3&gt;正确解决方案&lt;/h3&gt;

&lt;p&gt;将：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;配置到：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/usr/local/server/dolphinscheduler/bin/env/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后重新执行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /usr/local/server/dolphinscheduler

bash ./bin/stop-all.sh

bash ./bin/start-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后验证：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -n &amp;quot;SEATUNNEL_HOME&amp;quot; \
/usr/local/server/dolphinscheduler/worker-server/conf/dolphinscheduler_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;以及：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;PID=$(pgrep -f &amp;#39;org.apache.dolphinscheduler.server.worker.WorkerServer&amp;#39; | head -1)

cat /proc/$PID/environ | tr &amp;#39;\0&amp;#39; &amp;#39;\n&amp;#39; | \
grep SEATUNNEL_HOME
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;预期：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;SEATUNNEL_HOME=/usr/local/server/seatunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;核心经验：在 DolphinScheduler 中，通过 &lt;code&gt;start-all.sh&lt;/code&gt; 启动服务时，应优先修改 &lt;code&gt;bin/env/&lt;/code&gt; 下的统一环境配置，而不是只修改各 Server 自己的 &lt;code&gt;conf/&lt;/code&gt; 环境文件。&lt;/strong&gt;&lt;/p&gt;
</description><pubDate>Tue, 29 Sep 2026 09:07:57 +0800</pubDate></item><item><title>Windows 精确触摸板实现 Mac 同款三指拖拽完整教程 </title><link>https://forstyle.cc/zblog/post/129.html</link><description>&lt;blockquote&gt;&lt;p&gt;很多用惯 Mac 的用户换到 Windows 笔记本后，最不习惯的就是触摸板缺少&lt;strong&gt;三指拖移&lt;/strong&gt;——三根手指轻放触控板直接拖动窗口、框选文字、拖拽文件，不用用力按压触摸板。
本文使用开源免费工具 &lt;code&gt;ThreeFingerDragOnWindows&lt;/code&gt;，仅适用于 &lt;strong&gt;Windows Precision 精确触摸板（PTP）&lt;/strong&gt;，老款 Synaptics/ELAN 非PTP触控板不支持该方案。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;一、前置检查：确认你的电脑是否支持&lt;/h2&gt;&lt;h3&gt;1. 检测精确触摸板&lt;/h3&gt;&lt;p&gt;按下快捷键 &lt;code&gt;Win + I&lt;/code&gt; 打开设置 → &lt;strong&gt;蓝牙和设备 → 触摸板&lt;/strong&gt;页面顶部出现文字：&lt;strong&gt;你的电脑有一个精确式触摸板&lt;/strong&gt;，才可以继续。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;如果没有该提示，代表是老旧触控板，底层不支持PTP手势，本方案无效。&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/08/202608261051413766073.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;2. 确认系统架构（选择对应安装包）&lt;/h3&gt;&lt;p&gt;任务栏右键打开&lt;strong&gt;任务管理器 → 性能 → CPU&lt;/strong&gt;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;普通AMD/Intel处理器（如R5 4600U、i5-1135G7等）→ &lt;strong&gt;x64&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Surface Pro X、国产ARM笔记本 → &lt;strong&gt;arm64&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;古董32位系统 → &lt;strong&gt;x86&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;绝大多数新款笔记本直接选择 &lt;code&gt;x64&lt;/code&gt; 版本。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;二、准备工作：关闭系统原生三指手势（关键！防止冲突）&lt;/h2&gt;&lt;p&gt;同样在 &lt;code&gt;Win+I → 蓝牙和设备 → 触摸板&lt;/code&gt;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;找到&lt;strong&gt;三指手势&lt;/strong&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;/li&gt;&lt;li&gt;&lt;p&gt;✅ 四指手势不受影响，可以保留「四指左右切换虚拟桌面」等原生功能&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;三、下载与安装 ThreeFingerDragOnWindows&lt;/h2&gt;&lt;h3&gt;方式1：GitHub 离线安装包（推荐，无网络商店限制）&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/08/202608261054042827491.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;项目开源地址：&lt;a href=&quot;https://github.com/ClementGre/ThreeFingerDragOnWindows&quot;&gt;https://github.com/ClementGre/ThreeFingerDragOnWindows&lt;/a&gt;在 Releases → Assets 里选择对应架构文件：&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;x64架构：&lt;code&gt;ThreeFingersDragOnWindows_2.0.7_x64.Msix&lt;/code&gt;（绝大多数用户）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;arm64架构：&lt;code&gt;ThreeFingersDragOnWindows_2.0.7_arm64.Msix&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;x86(32位)：&lt;code&gt;ThreeFingersDragOnWindows_2.0.7_x86.Msix&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;.msix&lt;/code&gt; 是微软官方安装包，双击即可安装；如果提示安装策略拦截：右键文件 → 属性 → 勾选【解除锁定】后重试。&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;方式2：微软商店一键安装（最简单，自动更新）&lt;/h3&gt;&lt;p&gt;直接打开微软商店，搜索 &lt;code&gt;ThreeFingerDragOnWindows&lt;/code&gt;，一键安装，和离线包内容完全一致。&lt;/p&gt;&lt;h2&gt;四、软件基础配置（安装完成后打开软件设置）&lt;/h2&gt;&lt;p&gt;打开 ThreeFingerDragOnWindows，按照下面参数配置：&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Enable three finger drag&lt;/strong&gt;：开启三指拖拽总开关&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Mouse button to be held while dragging：选择 &lt;code&gt;Left button&lt;/code&gt;（模拟左键拖拽）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Delay before drag starts（触发延迟）：推荐 &lt;code&gt;200~500ms&lt;/code&gt;，数值越高越不容易误触&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Run on startup&lt;/strong&gt;：勾选开机自启&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Elevated mode（管理员模式）&lt;/strong&gt;：勾选后可以拖动任务管理器、安装弹窗这类管理员权限窗口（强烈建议开启）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;查看检测状态：显示绿色文字 &lt;code&gt;Touchpad exists and is registered!&lt;/code&gt; = 触摸板识别成功&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;拖拽灵敏度、鼠标加速默认即可，新手无需额外调整&lt;/p&gt;&lt;/blockquote&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;五、使用方法（和Mac完全一致）&lt;/h2&gt;&lt;p&gt;三根手指同时轻放在触摸板上直接滑动：&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; class=&quot; list-paddingleft-2&quot;&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;strong&gt;抬起任意一根手指，拖拽动作立刻结束&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;六、常见故障排查&lt;/h2&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;三根手指放上去没有任何反应&lt;/p&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;确认是Precision精确触摸板&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;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;频繁误触，随便放三根手指就触发拖拽&lt;/p&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;调高触发延迟（调到400~500ms），降低拖拽灵敏度&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;普通窗口可以拖动，任务管理器/安装弹窗拖不动&lt;/p&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;在软件设置里开启 &lt;code&gt;Elevated mode&lt;/code&gt;，重启软件生效&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;msix安装包无法安装&lt;/p&gt;&lt;/li&gt;&lt;ul style=&quot;list-style-type: disc;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;优先改用微软商店直接安装&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ol&gt;&lt;h2&gt;七、拓展方案（可选，丰富更多Mac手势）&lt;/h2&gt;&lt;p&gt;ThreeFingerDragOnWindows&lt;strong&gt;只专注实现三指拖移&lt;/strong&gt;，如果你想要更多自定义手势（四指收拢打开开始菜单、媒体控制等），可以搭配下面工具：&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;GestureSign&lt;/strong&gt;：免费可视化手势自定义工具，功能丰富，适合需要大量自定义手势的用户&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;AutoHotkey&lt;/strong&gt;：脚本级自定义，自由度天花板，适合有基础的进阶玩家&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;外接妙控板Magic Trackpad&lt;/strong&gt;：搭配开源驱动，可以实现无限接近原生Mac触摸板完整体验&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;八、局限说明（硬件底层限制，无法完美复刻Mac）&lt;/h2&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;普通Windows触摸板&lt;strong&gt;没有Force Touch压力感应、线性马达触觉反馈&lt;/strong&gt;，无法实现按压预览、力度反馈&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Windows底层滚动惯性算法和macOS不同，即便全部设置完成，顺滑度依然存在细微差距&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;老旧Win32软件对手势支持不一致，部分程序会吞掉触摸板手势，识别稳定性不如Mac全局统一&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;补充：原生触摸板基础Mac风格配置（搭配三指拖移一起用）&lt;/h2&gt;&lt;p&gt;在系统触摸板设置中额外开启：&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; class=&quot; list-paddingleft-2&quot;&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;✅ 自然滚动（滚动方向和Mac保持一致）&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;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Wed, 26 Aug 2026 10:09:55 +0800</pubDate></item><item><title>NyaTerm 最近发现的一款好用的运维工具分享</title><link>https://forstyle.cc/zblog/post/128.html</link><description>&lt;p&gt;NyaTerm 是国产的&lt;span style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-family: ui-sans-serif, system-ui, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;, &amp;quot;Noto Color Emoji&amp;quot;; line-height: 24px; text-wrap: wrap; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;免费开源、跨平台现代化一站式终端运维客户端&lt;/span&gt;，对标 Xshell、MobaXterm、WindTerm，由 Rust+Tauri+React 开发，全平台无付费阉割功能，主打一套软件搞定服务器远程登录、文件传输、设备调试、服务器监控等全流程运维工作。&lt;span style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-family: ui-sans-serif, system-ui, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;, &amp;quot;Noto Color Emoji&amp;quot;; line-height: 24px; text-wrap: wrap; display: inline-block; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;div style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-family: ui-sans-serif, system-ui, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;, &amp;quot;Noto Color Emoji&amp;quot;; line-height: 24px; text-wrap: wrap; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;h3 style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 16px; font-weight: 400; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;span style=&quot;text-decoration: none;&quot;&gt;1. 支持连接协议&lt;/span&gt;&lt;/h3&gt;&lt;/div&gt;&lt;div style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-family: ui-sans-serif, system-ui, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;, &amp;quot;Noto Color Emoji&amp;quot;; line-height: 24px; text-wrap: wrap; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;div style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; display: flex; flex: 0 1 auto; flex-direction: column; justify-content: normal; align-items: normal; padding: 0px; margin: 0px 0px 8px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;div style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;div messageid=&quot;52234514504670466&quot; pluginconfig=&quot;[object Object]&quot; style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px 0px 8px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;div style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;div style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;table&gt;&lt;thead style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;tr style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot; class=&quot;firstRow&quot;&gt;&lt;th style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; text-align: center; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);&quot;&gt;协议类型&lt;/th&gt;&lt;th style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; text-align: center; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);&quot;&gt;适用场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;tr style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;SSH（核心）&lt;/td&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;Linux 服务器远程登录、跳板机、端口转发、OTP 二次验证&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;内置 SFTP&lt;/td&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;服务器文件上传下载，无需单独装 WinSCP&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;本地 Shell&lt;/td&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;Windows CMD/PowerShell、macOS/Linux 原生终端&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;Telnet&lt;/td&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;老旧交换机、工业老旧设备调试&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;Serial 串口&lt;/td&gt;&lt;td style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;嵌入式开发板、网络交换机 Console 调试&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-family: ui-sans-serif, system-ui, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;, &amp;quot;Noto Color Emoji&amp;quot;; line-height: 24px; text-wrap: wrap; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;h3 style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-size: 16px; font-weight: 400; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;2. 核心特色功能&lt;/h3&gt;&lt;/div&gt;&lt;div style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; font-family: ui-sans-serif, system-ui, sans-serif, &amp;quot;Apple Color Emoji&amp;quot;, &amp;quot;Segoe UI Emoji&amp;quot;, &amp;quot;Segoe UI Symbol&amp;quot;, &amp;quot;Noto Color Emoji&amp;quot;; line-height: 24px; text-wrap: wrap; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; margin: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot;&gt;&lt;ol style=&quot;font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: normal; font-variation-settings: normal; font-variant-position: normal; font-stretch: normal; line-height: 24px; flex: 0 1 auto; flex-direction: row; justify-content: normal; align-items: normal; padding: 0px; background-image: none; background-position: 0% 0%; background-size: auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box;&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;多标签、横竖分屏，单界面同时管理多台服务器；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;侧边一键查看服务器 CPU / 内存 / 磁盘 / GPU 实时监控、Docker 可视化管理；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;内置 AI 终端助手：选中报错自动解析、自然语言生成运维命令；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;一键导入 Xshell、MobaXterm、WindTerm 旧会话配置；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;连接配置本地加密、云同步备份；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;终端操作录制回放、快捷命令模板、SSH 隧道内网穿透。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;下载地址&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;官网：&lt;a title=&quot;autolink&quot; current=&quot;[object Object]&quot; chatability=&quot;{}&quot; messageextra=&quot;[object Object]&quot; href=&quot;https://link.wtturl.cn/?target=https%3A%2F%2Fnyaterm.app&amp;scene=im&amp;aid=497858&amp;lang=zh&quot; target=&quot;_blank&quot; style=&quot;color: rgb(0, 87, 255);font-variant-numeric: normal;font-variant-east-asian: normal;font-variant-alternates: normal;font-kerning: auto;font-optical-sizing: auto;font-feature-settings: normal;font-variation-settings: normal;font-variant-position: normal;font-stretch: normal;line-height: 24px;display: inline;flex: 0 1 auto;flex-direction: row;justify-content: normal;align-items: normal;padding: 0px;background-image: none;background-position: 0% 0%;background-size: auto;background-repeat: repeat;background-attachment: scroll;background-origin: padding-box;background-clip: border-box&quot;&gt;https://nyaterm.app&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;a title=&quot;autolink&quot; current=&quot;[object Object]&quot; chatability=&quot;{}&quot; messageextra=&quot;[object Object]&quot; href=&quot;https://link.wtturl.cn/?target=https%3A%2F%2Fnyaterm.app&amp;scene=im&amp;aid=497858&amp;lang=zh&quot; target=&quot;_blank&quot; style=&quot;color: rgb(0, 87, 255);font-variant-numeric: normal;font-variant-east-asian: normal;font-variant-alternates: normal;font-kerning: auto;font-optical-sizing: auto;font-feature-settings: normal;font-variation-settings: normal;font-variant-position: normal;font-stretch: normal;line-height: 24px;display: inline;flex: 0 1 auto;flex-direction: row;justify-content: normal;align-items: normal;padding: 0px;background-image: none;background-position: 0% 0%;background-size: auto;background-repeat: repeat;background-attachment: scroll;background-origin: padding-box;background-clip: border-box&quot;&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/08/202608101012073536896.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;GitHub 发行页：&lt;a title=&quot;autolink&quot; current=&quot;[object Object]&quot; chatability=&quot;{}&quot; messageextra=&quot;[object Object]&quot; href=&quot;https://link.wtturl.cn/?target=https%3A%2F%2Fgithub.com%2Fnyakang%2Fnyaterm%2Freleases&amp;scene=im&amp;aid=497858&amp;lang=zh&quot; target=&quot;_blank&quot; style=&quot;color: rgb(0, 87, 255);font-variant-numeric: normal;font-variant-east-asian: normal;font-variant-alternates: normal;font-kerning: auto;font-optical-sizing: auto;font-feature-settings: normal;font-variation-settings: normal;font-variant-position: normal;font-stretch: normal;line-height: 24px;display: inline;flex: 0 1 auto;flex-direction: row;justify-content: normal;align-items: normal;padding: 0px;background-image: none;background-position: 0% 0%;background-size: auto;background-repeat: repeat;background-attachment: scroll;background-origin: padding-box;background-clip: border-box&quot;&gt;https://github.com/nyakang/nyaterm/releases&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;界面&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/08/202608101012192002570.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;显卡进程查看&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/08/202608101012472963986.png&quot; alt=&quot;image.png&quot;/&gt;硬件资源查看&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/08/202608101012587122679.png&quot; alt=&quot;image.png&quot;/&gt;docker管理&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/08/202608101013161050095.png&quot; alt=&quot;image.png&quot;/&gt;AI集成&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/08/202608101013309067228.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Mon, 10 Aug 2026 10:07:50 +0800</pubDate></item><item><title>windows端口转发，可转发WSL端口</title><link>https://forstyle.cc/zblog/post/127.html</link><description>&lt;p&gt;windows自带有端口转发命令，没必要安装其他管理软件&lt;br/&gt;&lt;/p&gt;&lt;p&gt;命令如下，将8003端口转发到WSL中的 8003 端口&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false;&quot;&gt;netsh&amp;nbsp;interface&amp;nbsp;portproxy&amp;nbsp;add&amp;nbsp;v4tov4&amp;nbsp;listenport=8003&amp;nbsp;listenaddress=0.0.0.0&amp;nbsp;connectport=8003&amp;nbsp;connectaddress=127.0.0.1&lt;/pre&gt;&lt;p&gt;查看全部的已配置的转发端口&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false;&quot;&gt;netsh&amp;nbsp;interface&amp;nbsp;portproxy&amp;nbsp;show&amp;nbsp;all&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;有个服务必须启动&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/07/202607271551552036848.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;服务启动后执行以下命令设置开机启动&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;::&amp;nbsp;立即启动&amp;nbsp;IP&amp;nbsp;Helper&amp;nbsp;服务
sc&amp;nbsp;start&amp;nbsp;iphlpsvc

::&amp;nbsp;设置服务开机自动启动（注意&amp;nbsp;start=&amp;nbsp;后面有空格，语法硬性要求）
sc&amp;nbsp;config&amp;nbsp;iphlpsvc&amp;nbsp;start=&amp;nbsp;auto&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Mon, 27 Jul 2026 15:48:06 +0800</pubDate></item><item><title>windwos和linux下获取当前使用的麦克风型号</title><link>https://forstyle.cc/zblog/post/126.html</link><description>&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;需求是浏览器获取当前麦克风型号，对不支持的型号做限制，&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;最开始想的通过js后去麦克风型号，但是在windows下测试的没问题，到linux系统下就不行了&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;历览器输入以下地址可以看到支持的硬件信息&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;brush:as3;toolbar:false&quot;&gt;chrome://media-internals/&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/07/202607241784874164512468.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;在windows下可以看到支持的型号，&lt;/p&gt;&lt;p&gt;但是在linux下看不到&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/07/202607241784874332449398.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIydGV4dCUyMiUzQSUyMndpbmR3byVFNCVCOCU4QiVFOSU5QyU4MCVFOCVBNiU4MSVFNCVCRCVCRiVFNyU5NCVBOCUyMHNvdW5kZGV2aWNlJTIwJUU1JUJBJTkzJUVGJUJDJThDbGludXglRTQlQjglOEIlRTQlQkQlQkYlRTclOTQlQTglMjBwdWxzZWN0bCUyMiU3RCU1RCU3RCU1RA==&quot;&gt;windwo下需要使用 sounddevice 库，linux下使用 pulsectl&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot; data-slate-fragment=&quot;JTVCJTdCJTIydHlwZSUyMiUzQSUyMnBhcmFncmFwaCUyMiUyQyUyMmNoaWxkcmVuJTIyJTNBJTVCJTdCJTIydGV4dCUyMiUzQSUyMndpbmR3byVFNCVCOCU4QiVFOSU5QyU4MCVFOCVBNiU4MSVFNCVCRCVCRiVFNyU5NCVBOCUyMHNvdW5kZGV2aWNlJTIwJUU1JUJBJTkzJUVGJUJDJThDbGludXglRTQlQjglOEIlRTQlQkQlQkYlRTclOTQlQTglMjBwdWxzZWN0bCUyMiU3RCU1RCU3RCU1RA==&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;pre class=&quot;brush:python;toolbar:false&quot;&gt;import&amp;nbsp;sys

def&amp;nbsp;get_windows_active_mic():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;Windows&amp;nbsp;系统：通过&amp;nbsp;COM/pycaw&amp;nbsp;接口精确获取当前默认输入的麦克风型号&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;import&amp;nbsp;sounddevice&amp;nbsp;as&amp;nbsp;sd

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;devices&amp;nbsp;=&amp;nbsp;sd.query_devices()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default_in_id&amp;nbsp;=&amp;nbsp;sd.default.device[0]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;print(&amp;quot;默认录音设备ID：&amp;quot;,&amp;nbsp;default_in_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;print(&amp;quot;默认录音设备ID：&amp;quot;,&amp;nbsp;devices[default_in_id])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;devices[default_in_id][&amp;#39;name&amp;#39;]

def&amp;nbsp;get_active_mic_pulsectl():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;import&amp;nbsp;pulsectl
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;pulsectl.Pulse(&amp;#39;get-mic-script&amp;#39;)&amp;nbsp;as&amp;nbsp;pulse:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;1.&amp;nbsp;获取默认&amp;nbsp;Source&amp;nbsp;的名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_info&amp;nbsp;=&amp;nbsp;pulse.server_info()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default_source_name&amp;nbsp;=&amp;nbsp;server_info.default_source_name

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;2.&amp;nbsp;遍历&amp;nbsp;Source&amp;nbsp;寻找描述信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;source&amp;nbsp;in&amp;nbsp;pulse.source_list():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;source.name&amp;nbsp;==&amp;nbsp;default_source_name:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;获取&amp;nbsp;friendly&amp;nbsp;名称
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;desc&amp;nbsp;=&amp;nbsp;source.proplist.get(&amp;#39;device.description&amp;#39;)&amp;nbsp;or&amp;nbsp;source.description
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;desc
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;未找到设备&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;f&amp;quot;获取失败:&amp;nbsp;{e}&amp;quot;

def&amp;nbsp;get_active_mic():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(sys.platform)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;根据操作系统自动路由&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;sys.platform.startswith(&amp;quot;win&amp;quot;):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;get_windows_active_mic()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elif&amp;nbsp;sys.platform.startswith(&amp;quot;linux&amp;quot;):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;get_active_mic_pulsectl()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;f&amp;quot;暂不支持的操作系统:&amp;nbsp;{sys.platform}&amp;quot;

if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;当前正在使用的麦克风型号:&amp;quot;,&amp;nbsp;get_active_mic())&lt;/pre&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/07/202607241784873900250372.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/07/202607241784873938115984.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description><pubDate>Fri, 24 Jul 2026 14:17:39 +0800</pubDate></item><item><title>VLLM服务端启动ChineseErrorCorrector3-4B模型</title><link>https://forstyle.cc/zblog/post/125.html</link><description>&lt;p&gt;先贴命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vllm&amp;nbsp;serve&amp;nbsp;/www/models/Qwen/ChineseErrorCorrector3-4B&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--max-model-len&amp;nbsp;10240&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--dtype&amp;nbsp;float16&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--max-num-batched-tokens&amp;nbsp;10240&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--gpu-memory-utilization&amp;nbsp;0.4&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--max-num-seqs&amp;nbsp;512&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--host&amp;nbsp;0.0.0.0&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--port&amp;nbsp;8004&amp;nbsp;\
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--served-model-name&amp;nbsp;csc-3B&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条命令的作用：&lt;span style=&quot;font-size: 16px;&quot;&gt;用 vLLM 高性能推理框架，把中文纠错模型 ChineseErrorCorrector3-4B 启动成兼容 OpenAI 接口格式的 HTTP 推理服务&lt;/span&gt;，外部程序可通过 8004 端口调用文本纠错能力。&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/07/202607121783864288474784.png&quot; alt=&quot;image-20260611145021-dksod7v.png&quot; data-href=&quot;&quot; /&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/07/202607121783864369730328.png&quot; alt=&quot;image-20260611145026-v1xrszm.png&quot; data-href=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;vllm servevLLM 提供的服务启动子命令，自动内置 OpenAI 兼容 REST API（/v1/completions、/v1/chat/completions），无需自己封装接口。&lt;/p&gt;
&lt;p&gt;/www/models/Qwen/ChineseErrorCorrector3-4B本地模型权重文件路径，指向 4B 参数中文文本纠错模型（CSC 错别字校对模型）。&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;code&gt;--max-model-len 10240&lt;/code&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;模型最大支持输入 + 输出总 token 长度 10240。&lt;/p&gt;
&lt;p&gt;适合长文本纠错场景（长篇文档、案卷、文章批量校对），超过长度会截断输入。&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;--dtype float16模型权重 / 推理计算使用 FP16 半精度：&lt;/p&gt;
&lt;blockquote&gt;显存占用减半，相比 FP32 节省大量显存；速度更快，绝大多数 Nvidia GPU 原生支持 FP16 加速；纠错类模型对精度不敏感，FP16 不会明显降低校对准确率。&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;code&gt;--max-num-batched-tokens 10240&lt;/code&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;单次推理批处理中，所有并发请求的 token 总和上限 10240。&lt;/p&gt;
&lt;p&gt;用来防止瞬间大量长文本请求撑爆显存，和 &lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;code&gt;max-model-len&lt;/code&gt;&lt;/span&gt; 配套管控显存负载。&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;--gpu-memory-utilization 0.4限制模型 + KV 缓存最多占用40% GPU 显存（0.4 = 40%）。&lt;/p&gt;
&lt;p&gt;预留 60% 显存给系统、其他进程、突发 KV 缓存；多模型混跑、显卡同时跑其他任务时常用低利用率；纯单模型独占显卡可调高至 0.8~0.95 提升并发。&lt;/p&gt;
&lt;p&gt;--max-num-seqs 512服务同时允许最多 512 条并行推理序列（并发请求）。&lt;/p&gt;
&lt;p&gt;短文本纠错场景可拉高，支持高 QPS；长文本场景建议降低，避免 KV 缓存暴涨 OOM。&lt;/p&gt;
&lt;p&gt;--host 0.0.0.0监听本机所有网卡地址，允许局域网 / 其他服务器远程访问接口；若写 127.0.0.1 则仅本机可调用，外部无法连接。&lt;/p&gt;
&lt;p&gt;--port 8004HTTP 服务监听端口，调用地址示例：http://服务器IP:8004/v1/chat/completions&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;code&gt;--served-model-name csc-3B&lt;/code&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;API 请求时需要指定的 &lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;code&gt;model&lt;/code&gt;&lt;/span&gt; 参数名称，不用填冗长模型路径。&lt;/p&gt;
&lt;p&gt;调用示例请求体片段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&quot;model&quot;:&amp;nbsp;&quot;csc-3B&quot;,
&amp;nbsp;&amp;nbsp;&quot;prompt&quot;:&amp;nbsp;&quot;需要纠错的原文&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description><pubDate>Sun, 12 Jul 2026 21:46:23 +0800</pubDate></item><item><title>windows右键管理</title><link>https://forstyle.cc/zblog/post/124.html</link><description>&lt;p&gt;windows11设计之初是为了简化右键，但是很多功能都隐藏起来了，要不通过shift + 右键出来详细右键，要不就得点详情来看，通过以下方法可以直接换为旧版的右键菜单。&lt;/p&gt;&lt;p&gt;开始菜单搜索cmd，以管理员身份打开&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/05/202605271779845975404304.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;运行以下命令&lt;br/&gt;&lt;/p&gt;&lt;pre class=&quot;brush:bash;toolbar:false&quot;&gt;reg.exe&amp;nbsp;add&amp;nbsp;&amp;quot;HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}&amp;quot;&amp;nbsp;/f
reg.exe&amp;nbsp;add&amp;nbsp;&amp;quot;HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32&amp;quot;&amp;nbsp;/f&amp;nbsp;/ve&lt;/pre&gt;&lt;p&gt;再执行&lt;/p&gt;&lt;pre class=&quot;brush:bash;toolbar:false&quot;&gt;taskkill&amp;nbsp;/f&amp;nbsp;/im&amp;nbsp;explorer.exe
start&amp;nbsp;explorer.exe&lt;/pre&gt;&lt;p&gt;然后点右键就是旧版的菜单了。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/05/202605271779846029938472.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;还有一个好用的右键菜单管理工具推荐。&lt;/p&gt;&lt;p&gt;ContextMenuManager&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/BluePointLilac/ContextMenuManager/releases/tag/3.3.3.1&quot; target=&quot;_blank&quot; textvalue=&quot;下载链接&quot;&gt;下载链接&lt;/a&gt;&lt;/p&gt;&lt;p&gt;下载完后不需要安装直接打开就能用&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/05/202605271779846115155709.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;可以管理文件、文件夹、目录等等各种右键菜单。&lt;/p&gt;&lt;p&gt;我现在在目录里面右键就只剩下这些了。&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://forstyle.cc/zblog/zb_users/upload/2026/05/202605271779846186351198.png&quot; alt=&quot;image.png&quot;/&gt;&lt;/p&gt;&lt;p&gt;简化了很多。&lt;br/&gt;&lt;/p&gt;</description><pubDate>Wed, 27 May 2026 09:35:57 +0800</pubDate></item></channel></rss>