[{"content":" 系列说明：本文是《Ubuntu 18.04 编译服务器实战》系列第二篇，讲装好系统后如何配置 Android 11–17 的编译环境。 系列第一篇《Ubuntu 18.04 装系统趟坑实录：U 盘启动 + 分区 + 黑屏（nomodeset）+ 花屏（LightDM）》解决装系统过程里的 U 盘启动、分区、swap、黑屏与花屏问题。 全文脱敏：所有内网 IP、账号、密码、公司邮箱域名、manifest/分支/编译目标均已替换为占位符，文末「参数对照表」一次性列出你要填的全部取值。\n〇、环境与前置 项 值（编译机实测） 系统 Ubuntu 18.04.6（内核 5.4.0-150，用户态 18.04） 账号 \u0026lt;BUILD_USER\u0026gt; 代码盘 独立分区挂载于 \u0026lt;CODE_DISK\u0026gt;（ext4，剩余空间充足） 默认 shell /bin/bash Python python→2.7.17（系统原生，Android 构建所需）、python3→3.6.9 ccache 3.4.1（apt 默认） 代码服务器 gerrit \u0026lt;GERRIT_HOST\u0026gt;:\u0026lt;GERRIT_PORT\u0026gt;，账号 \u0026lt;GERRIT_USER\u0026gt;（公钥已上传） ⚠️ 步骤 1（安装系统）：不在本文范围，详见系列第一篇。本文假设你已有一台能进桌面的 18.04 编译机。\n一、常用 app（步骤 2） 1 sudo apt-get install patch bison flex gettext tcl net-tools repo 注：repo 这里装的是 apt 旧启动器（v1.23，仅 py2），不能直接用，见步骤 9 的修复。\n二、Samba 共享（步骤 3） Windows 端通过 Samba 直接读写代码盘，免逐文件 scp。\n1 2 sudo apt-get install samba samba-common sudo vi /etc/samba/smb.conf 在 [global] 之后添加（把 \u0026lt;BUILD_USER\u0026gt; 换成实际编译账号）：\n1 2 3 4 5 6 7 8 9 10 11 12 [Project] comment = This is samba dir for project path = \u0026lt;CODE_DISK\u0026gt;/ public = yes writable = yes valid users = \u0026lt;BUILD_USER\u0026gt; ;create mask = 0777 ;directory mask = 0777 ;force user = nobody ;force group = nogroup available = yes browseable = yes 设置 samba 密码并重启：\n1 2 sudo smbpasswd -a \u0026lt;BUILD_USER\u0026gt; # 输入两次密码 sudo service smbd restart 验证：sudo systemctl is-active smbd 应为 active；Windows 端用 \\\\\u0026lt;BUILD_SERVER_IP\u0026gt;\\Project 访问。\n三、SSH 服务（步骤 4） 1 2 3 sudo apt-get install openssh-server ssh sudo systemctl enable ssh sudo systemctl start ssh 四、Android 编译依赖（步骤 5） 1 2 3 4 5 sudo apt-get install -y git gnupg flex bison gperf build-essential zip curl \\ libc6-dev x11proto-core-dev g++-multilib gcc-multilib tofrodos python-markdown \\ libxml2-utils xsltproc lib32z-dev lib32z1 libc6-dev-i386 libgl1-mesa-dev \\ libncurses5 lib32ncurses5-dev libssl-dev libx11-dev m4 unzip zip zlib1g-dev \\ bsdmainutils cgpt libswitch-perl bc rsync xxd git-core parallel ⚠️ 清单里的 lib32z-dev 是笔误，Ubuntu 18.04 真实包名是 lib32z1-dev；apt 装 lib32z-dev 会报「无法定位」。实际需要的 32 位 zlib 由 lib32z1-dev 提供，上面已包含，无需单独改。 git-core 是过渡虚包，装了 git 即满足，显示 MISSING 可忽略。\n五、生成公钥 + 上传 gerrit（步骤 6–7） 1 2 ssh-keygen -t ed25519 -C \u0026#34;\u0026lt;YOUR_EMAIL\u0026gt;\u0026#34; cat ~/.ssh/id_ed25519.pub # 复制整行 把 .pub 内容加到 gerrit 账户 \u0026lt;GERRIT_USER\u0026gt; 的 SSH Keys 设置页。\n验证：ssh -p \u0026lt;GERRIT_PORT\u0026gt; \u0026lt;GERRIT_USER\u0026gt;@\u0026lt;GERRIT_HOST\u0026gt; \u0026quot;gerrit version\u0026quot; 应返回 gerrit version 3.8.0。\n六、Git 全局配置（步骤 8） 1 2 git config --global user.name \u0026#34;\u0026lt;GERRIT_USER\u0026gt;\u0026#34; git config --global user.email \u0026#34;\u0026lt;YOUR_EMAIL\u0026gt;\u0026#34; 七、配置 ccache（步骤 9，自动按平台+Android 版本切缓存目录） 原理：Android 全量编译动辄数小时，ccache 把预处理后的中间产物按哈希缓存，重编/换分支时命中缓存可大幅提速。难点是不同平台（UNISOC/MTK）和不同 Android 版本（A11–A17）的缓存不能混用——ABI、头文件、编译选项都不同，混用会编译异常。方案是「每个版本一个独立缓存目录 + 进代码树自动切换」。\n7.1 安装 1 2 sudo apt-get install -y ccache ccache --version # 确认 3.4.1 7.2 写入 ~/.bashrc 配置块 把下面整段追加到 ~/.bashrc（写入前先 cp ~/.bashrc ~/.bashrc.bak.时间戳）。将下文所有 \u0026lt;CODE_DISK\u0026gt; 替换为你的代码盘挂载点（如 /media/Project）。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 # \u0026gt;\u0026gt;\u0026gt; ccache for Android builds \u0026gt;\u0026gt;\u0026gt; export USE_CCACHE=1 export CCACHE_EXEC=$(command -v ccache) export CC_WRAPPER=$(command -v ccache) export CXX_WRAPPER=$(command -v ccache) export CCACHE_COMPILERCHECK=content export CCACHE_SLOPPINESS=include_file_mtime,file_macro export CCACHE_BASEDIR=\u0026lt;CODE_DISK\u0026gt; export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache export CCACHE_CPP2=1 _auto_ccache() { [ \u0026#34;$PWD\u0026#34; = \u0026#34;${_ccache_last_dir:-}\u0026#34; ] \u0026amp;\u0026amp; return 0 _ccache_last_dir=\u0026#34;$PWD\u0026#34; local d=\u0026#34;$PWD\u0026#34; f=\u0026#34;\u0026#34; bid=\u0026#34;\u0026#34; old=\u0026#34;$CCACHE_DIR\u0026#34; ap=\u0026#34;\u0026#34; mc=\u0026#34;\u0026#34; is_mtk=\u0026#34;\u0026#34; mtk=\u0026#34;\u0026#34; sprd=\u0026#34;\u0026#34; while [ \u0026#34;$d\u0026#34; != \u0026#34;/\u0026#34; ]; do if [ -f \u0026#34;$d/sys/build/make/core/build_id.mk\u0026#34; ]; then f=\u0026#34;$d/sys/build/make/core/build_id.mk\u0026#34;; break; fi if [ -f \u0026#34;$d/idh.code/build/make/core/build_id.mk\u0026#34; ]; then f=\u0026#34;$d/idh.code/build/make/core/build_id.mk\u0026#34;; break; fi mc=$(ls -d \u0026#34;$d\u0026#34;/sprd.mocor*/build/make/core/build_id.mk 2\u0026gt;/dev/null | head -1) [ -n \u0026#34;$mc\u0026#34; ] \u0026amp;\u0026amp; { f=\u0026#34;$mc\u0026#34;; break; } ap=$(ls -d \u0026#34;$d\u0026#34;/mtk67xx_t*/alps/build/make/core/build_id.mk 2\u0026gt;/dev/null | head -1) [ -n \u0026#34;$ap\u0026#34; ] || ap=$(ls -d \u0026#34;$d\u0026#34;/mtk67xx_*/alps/build/make/core/build_id.mk 2\u0026gt;/dev/null | head -1) [ -n \u0026#34;$ap\u0026#34; ] \u0026amp;\u0026amp; { f=\u0026#34;$ap\u0026#34;; break; } if [ -f \u0026#34;$d/alps/build/make/core/build_id.mk\u0026#34; ]; then f=\u0026#34;$d/alps/build/make/core/build_id.mk\u0026#34;; break; fi if [ -f \u0026#34;$d/build/make/core/build_id.mk\u0026#34; ]; then f=\u0026#34;$d/build/make/core/build_id.mk\u0026#34;; break; fi d=$(dirname \u0026#34;$d\u0026#34;) done if [ -z \u0026#34;$f\u0026#34; ]; then [ \u0026#34;$CCACHE_DIR\u0026#34; != \u0026#34;\u0026lt;CODE_DISK\u0026gt;/.ccache\u0026#34; ] \u0026amp;\u0026amp; export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache return 0 fi bid=$(sed -nE \u0026#39;s/^[[:space:]]*BUILD_ID[[:space:]]*:?=[[:space:]]*\u0026#34;?([^\u0026#34;[:space:]#]+)\u0026#34;?.*$/\\1/p\u0026#39; \u0026#34;$f\u0026#34; | head -n1) [ -z \u0026#34;$bid\u0026#34; ] \u0026amp;\u0026amp; { export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache; return 0; } case \u0026#34;$f\u0026#34; in *alps*) is_mtk=1 ;; */sprd.mocor*) is_mtk=0 ;; */idh.code/*) is_mtk=0 ;; */sys/*) sysroot=\u0026#34;${f%/sys/build/make/core/build_id.mk}\u0026#34; [ -d \u0026#34;$sysroot/sys/vendor/mediatek\u0026#34; ] \u0026amp;\u0026amp; mtk=1 [ -d \u0026#34;$sysroot/sys/vendor/sprd\u0026#34; ] \u0026amp;\u0026amp; sprd=1 if [ \u0026#34;$mtk\u0026#34; = \u0026#34;1\u0026#34; ] \u0026amp;\u0026amp; [ -z \u0026#34;$sprd\u0026#34; ]; then is_mtk=1 elif [ \u0026#34;$sprd\u0026#34; = \u0026#34;1\u0026#34; ] \u0026amp;\u0026amp; [ -z \u0026#34;$mtk\u0026#34; ]; then is_mtk=0 else is_mtk=unknown; fi ;; *) is_mtk=unknown ;; esac [ \u0026#34;$is_mtk\u0026#34; = \u0026#34;unknown\u0026#34; ] \u0026amp;\u0026amp; { export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache; return 0; } if [ \u0026#34;$is_mtk\u0026#34; = \u0026#34;1\u0026#34; ]; then case \u0026#34;$bid\u0026#34; in SP*|SQ*|SD*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_mtk_a12 ;; TP*|TQ*|TD*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_mtk_a13 ;; UP*|UQ*|UD*|AP1A*|AP2A*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_mtk_a14 ;; AP3A*|AP4A*|AP5A*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_mtk_a15 ;; BP*|BQ*|BD*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_mtk_a16 ;; *) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache ;; esac else case \u0026#34;$bid\u0026#34; in RP*|RQ*|RD*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_a11 ;; SP*|SQ*|SD*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_a12 ;; TP*|TQ*|TD*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_a13 ;; UP*|UQ*|UD*|AP1A*|AP2A*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_a14 ;; AP3A*|AP4A*|AP5A*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_a15 ;; BP*|BQ*|BD*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_a16 ;; CP*|CQ*|CD*) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache_a17 ;; *) export CCACHE_DIR=\u0026lt;CODE_DISK\u0026gt;/.ccache ;; esac fi } if declare -p PROMPT_COMMAND \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then if declare -p PROMPT_COMMAND 2\u0026gt;/dev/null | grep -q \u0026#39;declare -a\u0026#39;; then case \u0026#34; ${PROMPT_COMMAND[*]} \u0026#34; in *\u0026#34; _auto_ccache \u0026#34;*) : ;; *) PROMPT_COMMAND+=(_auto_ccache) ;; esac else printf \u0026#39;%s\u0026#39; \u0026#34;$PROMPT_COMMAND\u0026#34; | grep -q \u0026#39;_auto_ccache\u0026#39; || PROMPT_COMMAND=\u0026#34;_auto_ccache;${PROMPT_COMMAND}\u0026#34; fi else PROMPT_COMMAND=\u0026#39;_auto_ccache\u0026#39; fi ccache_s() { for d in \u0026lt;CODE_DISK\u0026gt;/.ccache*; do [ -d \u0026#34;$d\u0026#34; ] || continue echo \u0026#34;=== ${d##*/} ===\u0026#34;; CCACHE_DIR=\u0026#34;$d\u0026#34; ccache -s done } # \u0026lt;\u0026lt;\u0026lt; ccache \u0026lt;\u0026lt;\u0026lt; ⚠️ 注意：本段按编译机实测保留了 CCACHE_CPP2=1。另一份《ccache 配置提示词》要求 ccache 3.x 不写 CCACHE_CPP2（因 run_second_cpp 默认 true）。若你的缓存命中异常，可对照该文档取舍。\n⚠️ 18.04 的 ~/.bashrc 第 8 行有 case $- in *i*) ;; *) return;; esac，非交互 shell 会提前 return，上面的块在非交互（脚本/SSH 命令）下不会加载。解决办法：另存一份无保护的 env 脚本到 \u0026lt;CODE_DISK\u0026gt;/.ccache_env.sh（内容同上，去掉 bashrc 的 return 保护），自动化构建前 source \u0026lt;CODE_DISK\u0026gt;/.ccache_env.sh 即可。\n7.3 建目录 + 持久化 max_size/compression 1 2 3 4 5 6 7 mkdir -p \u0026lt;CODE_DISK\u0026gt;/.ccache \u0026lt;CODE_DISK\u0026gt;/.ccache_a16 for d in \u0026lt;CODE_DISK\u0026gt;/.ccache*; do [ -d \u0026#34;$d\u0026#34; ] || continue CCACHE_DIR=\u0026#34;$d\u0026#34; ccache -M 100G CCACHE_DIR=\u0026#34;$d\u0026#34; ccache -o compression=true done chmod -R 777 \u0026lt;CODE_DISK\u0026gt;/.ccache \u0026lt;CODE_DISK\u0026gt;/.ccache_a16 soong 环境变量白名单会过滤 CCACHE_MAXSIZE/CCACHE_COMPRESS，所以必须写进 ccache.conf，不要在 .bashrc 里 export CCACHE_MAXSIZE。\n八、下载代码（步骤 10）—— 含 3 处必踩坑修复 代码必须下载到子目录，不要直接放 \u0026lt;CODE_DISK\u0026gt; 根： mkdir -p \u0026lt;CODE_DISK\u0026gt;/\u0026lt;BUILD_USER\u0026gt;/Unisoc/A16 \u0026amp;\u0026amp; cd \u0026lt;CODE_DISK\u0026gt;/\u0026lt;BUILD_USER\u0026gt;/Unisoc/A16\n8.1 坑 1：apt 的 repo 是 py2 启动器，拉起 py3 的 main.py 会崩 现象：repo init 报 SyntaxError / TypeError: startswith first arg must be bytes。 原因：/usr/bin/repo(v1.23) shebang 是 #!/usr/bin/python(=2.7)，它从镜像下载现代 py3 main.py 到 .repo/repo/，仍用 py2 执行 → 报错。\n修复：装现代 py3 启动器到 /usr/local/bin/repo（PATH 优先于 /usr/bin，不动 apt 原文件）：\n1 2 3 4 5 6 sudo apt-get install -y git cd /tmp \u0026amp;\u0026amp; git clone --depth 1 https://mirrors.tuna.tsinghua.edu.cn/git/git-repo git-repo-launcher sudo cp /tmp/git-repo-launcher/repo /usr/local/bin/repo sudo chmod +x /usr/local/bin/repo which -a repo # 应优先显示 /usr/local/bin/repo repo --version # 应显示 v2.x（tracking stable） 8.2 坑 2：gerrit 主机密钥未信任 现象：repo sync 报 Host key verification failed。 修复：\n1 2 3 mkdir -p ~/.ssh \u0026amp;\u0026amp; chmod 700 ~/.ssh ssh-keyscan -p \u0026lt;GERRIT_PORT\u0026gt; \u0026lt;GERRIT_HOST\u0026gt; \u0026gt;\u0026gt; ~/.ssh/known_hosts sort -u ~/.ssh/known_hosts -o ~/.ssh/known_hosts 8.3 坑 3：manifest 写死 \u0026lt;MANIFEST_OWNER\u0026gt;@，但公钥上传的是 \u0026lt;GERRIT_USER\u0026gt; 现象：repo sync 报 \u0026lt;MANIFEST_OWNER\u0026gt;@\u0026lt;GERRIT_HOST\u0026gt;:\u0026lt;GERRIT_PORT\u0026gt;: Permission denied (publickey)；而 ssh -p \u0026lt;GERRIT_PORT\u0026gt; \u0026lt;GERRIT_USER\u0026gt;@\u0026lt;GERRIT_HOST\u0026gt; \u0026quot;gerrit version\u0026quot; 正常。 原因：manifest \u0026lt;MANIFEST_XML\u0026gt; 的 \u0026lt;remote fetch=\u0026quot;ssh://\u0026lt;MANIFEST_OWNER\u0026gt;@\u0026lt;GERRIT_HOST\u0026gt;:\u0026lt;GERRIT_PORT\u0026gt;/\u0026quot;\u0026gt; 用了仓库原主人的账号，而我们的公钥注册在 \u0026lt;GERRIT_USER\u0026gt; 下。\n修复（不改 manifest、不改源码，用 git 全局改写）：\n1 2 3 4 5 # 先确认 \u0026lt;GERRIT_USER\u0026gt; 对这些项目有读权限 git ls-remote ssh://\u0026lt;GERRIT_USER\u0026gt;@\u0026lt;GERRIT_HOST\u0026gt;:\u0026lt;GERRIT_PORT\u0026gt;/\u0026lt;MANIFEST_PROJECT\u0026gt;/sys # 应能列出 HEAD # 改写 fetch 用户 git config --global url.\u0026#34;ssh://\u0026lt;GERRIT_USER\u0026gt;@\u0026lt;GERRIT_HOST\u0026gt;:\u0026lt;GERRIT_PORT\u0026gt;/\u0026#34;.insteadOf \\ \u0026#34;ssh://\u0026lt;MANIFEST_OWNER\u0026gt;@\u0026lt;GERRIT_HOST\u0026gt;:\u0026lt;GERRIT_PORT\u0026gt;/\u0026#34; 8.4 下载命令 1 2 3 4 5 6 7 8 9 cd \u0026lt;CODE_DISK\u0026gt;/\u0026lt;BUILD_USER\u0026gt;/Unisoc/A16 repo init --repo-url=https://mirrors.tuna.tsinghua.edu.cn/git/git-repo \\ -u ssh://\u0026lt;GERRIT_USER\u0026gt;@\u0026lt;GERRIT_HOST\u0026gt;:\u0026lt;GERRIT_PORT\u0026gt;/manifest.git -m \u0026lt;MANIFEST_XML\u0026gt; repo sync -j16 -c --no-clone-bundle repo start master --all # 关闭各子仓 core.fileMode（避免权限误判） for d in google nvandmodem sys vnd zcommon zlib zprj; do [ -d \u0026#34;$d\u0026#34; ] \u0026amp;\u0026amp; (cd \u0026#34;$d\u0026#34; \u0026amp;\u0026amp; git config core.fileMode false) done 附：Ubuntu 18.04 自带 OpenSSH 7.6 不支持 git 协议 v2 的 SetEnv 指令，日志会出现无害警告 command-line: line 0: Bad configuration option: setenv，git 会优雅回退，不影响同步。 若要消除：git config --global protocol.version 1（步骤 11/12 前执行即可）。\n九、切基线 + 冷/热编译（步骤 11–12） 1 2 3 4 5 6 cd \u0026lt;CODE_DISK\u0026gt;/\u0026lt;BUILD_USER\u0026gt;/Unisoc/A16 cd zcommon git checkout \u0026lt;BRANCH_A16\u0026gt; ./create_ln.sh cd .. ./zswitch_branch.sh \u0026lt;BRANCH_A16\u0026gt; 编译命令（具体目标按你的项目替换）： 1 2 cd \u0026lt;CODE_DISK\u0026gt;/\u0026lt;BUILD_USER\u0026gt;/Unisoc/A16 source zmake \u0026lt;BUILD_TARGET\u0026gt; 冷编译：清空缓存（ccache -C）后首次完整编译，填充缓存。 热编译：删 out/ 后再次完整编译，缓存已热 → 应接近 100% 命中。 每次编译生成完成 log；最后用 ccache -s 两次统计做对比，出报告。 本步在代码全量下载完成后执行。实测数据（冷/热耗时对比、命中率）请在你环境跑完后回填。\n十、进阶：长任务「自愈式检测」（可选，但强烈推荐） 问题背景：判断「代码下载完成 / 编译完成」若依赖「本地后台跑 SSH 长任务 + 等系统通知」，有两个致命脆弱点：\nSSH 会话断开即死：长任务是 SSH 会话的子进程，会话一断就被 SIGHUP 杀掉。 通知不触发就干等：后台完成通知未送达则无任何兜底检测。 修复：改成「远端 setsid 脱离会话 + 完成哨兵文件 + 本地轮询器」三段式，彻底不依赖通知机制。\n段 做法 作用 ① 脱离会话 setsid bash job.sh \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 \u0026lt;/dev/null \u0026amp; 启动长任务 任务成为独立 session，SSH 断开也继续跑 ② 完成哨兵 每个 job 脚本末尾 echo $rc \u0026gt; /path/.xxx_done（含退出码，0=成功） 「完成」= 哨兵文件存在，随时可查 ③ 本地轮询 轮询器周期 SSH 检查哨兵 + 监控目录大小增长（连续不增长→卡死预警） 轮询器本身后台运行，检测到哨兵即结束并通知 1 2 3 4 5 6 # 远端：setsid 脱离会话启动 ssh \u0026lt;BUILD_USER\u0026gt;@\u0026lt;BUILD_SERVER_IP\u0026gt; setsid bash \u0026lt;CODE_DISK\u0026gt;/\u0026lt;BUILD_USER\u0026gt;/Unisoc/A16/download_code.sh \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 \u0026lt;/dev/null \u0026amp; # 本地：轮询检测（检测到 .download_done 即结束并通知） bash watch_job.sh \u0026lt;CODE_DISK\u0026gt;/\u0026lt;BUILD_USER\u0026gt;/Unisoc/A16/.download_done STEP10 220 30 配套脚本（download_code.sh / switch_branch.sh / build_cold.sh / build_hot.sh / watch_job.sh）按你的代码目录落地即可，内部 pkill 不要用含脚本名的 pkill -f，否则会误杀执行命令的 shell 自身——改用独立 kill 脚本或按 PID 精确杀。\n十一、验证清单（编译机实测） 检查项 结果 18.04.6 ✅ 常用 app / samba / ssh / android 依赖 ✅ ed25519 公钥 + gerrit 可达 ✅ gerrit version 3.8.0 git config ✅ \u0026lt;GERRIT_USER\u0026gt; / \u0026lt;YOUR_EMAIL\u0026gt; ccache 3.4.1 + 分目录 100G ✅ 现代 py3 repo 启动器 ✅ v2.x @ /usr/local/bin/repo gerrit 主机密钥 ✅ known_hosts \u0026lt;MANIFEST_OWNER\u0026gt;→\u0026lt;GERRIT_USER\u0026gt; insteadOf ✅ 读权限已验证 代码下载到子目录 ✅ \u0026lt;CODE_DISK\u0026gt;/\u0026lt;BUILD_USER\u0026gt;/Unisoc/A16/ 步骤 11/12（切基线 + 冷热处理） ⏳ 待下载完成后执行 十二、参数对照表（脱敏 · 全文要替换的都在这里） 把下表占位符全部替换成你自己的环境值，文章里的命令即可直接跑。这是日后回看时一眼定位「要配置哪些参数」的唯一清单。\n占位符 含义 你的取值（示例） \u0026lt;BUILD_SERVER_IP\u0026gt; 编译机内网 IP 如 192.168.1.205 \u0026lt;BUILD_USER\u0026gt; 编译机登录账号 如 user \u0026lt;BUILD_PASSWORD\u0026gt; 编译机登录/samba 密码 自建强密码，勿用弱口令 \u0026lt;CODE_DISK\u0026gt; 代码盘挂载点 如 /media/Project \u0026lt;GERRIT_HOST\u0026gt; 代码服务器（gerrit）主机 如 192.168.1.47 或域名 \u0026lt;GERRIT_PORT\u0026gt; gerrit SSH 端口 默认 29418 \u0026lt;GERRIT_USER\u0026gt; gerrit 账号 / git user.name 你的账号 \u0026lt;YOUR_EMAIL\u0026gt; git user.email 与 SSH 公钥注释 你的邮箱 \u0026lt;MANIFEST_XML\u0026gt; 项目 manifest 文件名 如 Sprd.Android14.4G.xml \u0026lt;MANIFEST_PROJECT\u0026gt; gerrit 上托管 manifest 的项目路径 如 Sprd.Android14.4G \u0026lt;MANIFEST_OWNER\u0026gt; manifest 中写死的原始 fetch 账号 原主人账号 \u0026lt;BRANCH_A16\u0026gt; 项目基线分支名 你的分支（如 xm_uni_branch_a16） \u0026lt;BUILD_TARGET\u0026gt; 具体编译目标（source zmake ...） 你的目标（如 zprj/.../WA09_CD3384_HONOR_GO） 说明：上表已剔除公司专属信息（原 qualicom.com.cn 邮箱域名、原 gerrit 账号 sjinqian/jqiang、原编译目标中的客户项目名等）。平台厂商名（UNISOC/展讯、MTK）属公开信息，予以保留以便检索。\n","permalink":"https://sikinzen.github.io/posts/ubuntu1804-android-build-env-setup/","summary":"无外网编译机装好后如何配 Android 11–17 构建环境：apt 依赖、ccache 分目录加速、gerrit 公钥、以及 repo 启动器/主机密钥/manifest 账号三坑修复。系列第二篇，含脱敏参数表。","title":"Ubuntu 18.04 配置 Android 11–17 编译环境：依赖、ccache 分目录与下载避坑"},{"content":" 系列说明：本文是《Ubuntu 18.04 编译服务器实战》系列第一篇，按安装时间线讲「装系统 + 图形排障」全过程（U 盘启动、分区、swap、黑屏、花屏）。 系列第二篇《Ubuntu 18.04 配置 Android 11–17 编译环境》讲装好系统后如何配置 Android 11–17 的编译环境（依赖、ccache 分目录、代码下载避坑）。 两篇都做了脱敏处理，文末「参数对照表」汇总了所有需要你按自己环境替换的占位符。\n〇、按安装阶段一眼分流 装一台「新硬件 + Ubuntu 18.04」机器，遇到的坑按时间先后排列如下。先定位自己卡在哪一阶段，再跳到对应章节。\n卡在哪个阶段 典型现象 跳到 ① 进不去 U 盘安装界面 插 U 盘、设 U 盘启动后仍进 grub rescue\u0026gt;，或直接从硬盘启动 一 ② 安装器分区异常 EFI 分区不知分多大；/dev/sdc 等整盘无分区表，+/- 按钮置灰 二 ③ 大内存要不要 swap 128G 内存，纠结是否开 swap、开多大 三 ④ 安装/启动黑屏 选 Install Ubuntu 后黑屏（NumLock 有响应），或装完重启进不去桌面 四 ⑤ 进桌面后花屏 能进登录/桌面但撕裂、糊屏、残影；TTY 与 startx 正常；同硬件 22.04 没事 五 共同前提：新硬件（新核显/新显卡）+ 老系统（18.04 内核 4.15 / Mesa 18.2）。根因各不相同，但都不是显卡坏了。\n一、进不去 U 盘安装界面（BIOS/UEFI 启动配置） 1.1 现象：进 GRUB rescue 而非安装界面 插好 Ubuntu 安装盘、设了 U 盘启动，屏幕却显示：\nBooting from Hard drive C: error: file `/boot/grub/i386-pc/normal.mod` not found. Entering rescue mode... grub rescue\u0026gt; 关键判断：Booting from Hard drive C: 说明机器根本没从 U 盘启动，而是读了硬盘上旧的、已损坏的 GRUB，才进 rescue。\n若同一张 U 盘在别的电脑能正常进安装界面，那问题就集中在这台机器的启动配置，而不是 U 盘本身。\n1.2 根因：启动模式 / 启动项没配对 排查项 说明 操作 1. BIOS 未真正保存 U 盘优先 改了 Boot Order 但没 F10 保存，或误选了 Hard Drive Boot 选项卡把 USB 调到首位 → Save \u0026amp; Exit 2. 启动模式不匹配 U 盘是 UEFI 制作，BIOS 却设 Legacy；或相反 BIOS 中切换 UEFI / Legacy(CSM)，与 U 盘一致 3. 选错 U 盘启动项 同个 U 盘可能同时出现带 \u0026ldquo;UEFI\u0026rdquo; 前缀和不带的两个项 分别试 UEFI: USB 和 USB HDD 4. Secure Boot / Fast Boot 拦截 部分主板跳过第三方 USB 启动 关闭 Secure Boot 和 Fast Boot 5. USB 接口兼容性 老主板在 USB 3.0 口识别不稳 换到 USB 2.0 口（黑色），台式机优先后置口 1.3 快速修复步骤 重启按 DEL / F2 进 BIOS / 启动菜单。 找 Boot Mode，确认与 U 盘制作方式一致（Rufus/UNetbootin 默认多为 UEFI）。 Boot 选项卡确认启动顺序首位是 U 盘。 保存退出，重启后立刻按 F11 手动调出一次性启动菜单，手动选 U 盘最稳妥（比改 Boot Order 更不易踩坑）。 1.4 原理：BIOS 与 UEFI 的区别与选择 对比项 BIOS / Legacy（传统） UEFI（现代） 全称 Basic Input/Output System Unified Extensible Firmware Interface 启动方式 通过 MBR 引导扇区读 GRUB 第一阶段 通过 FAT32 的 EFI 系统分区读取 .efi 文件 硬盘分区表 只认 MBR（最大 2TB、4 主分区） 支持 GPT（盘更大、分区更多） 启动速度 较慢 更快 安全性 无签名校验 支持 Secure Boot 兼容性 老系统、老主板、MBR 盘 Win8+ / 现代 Linux（含 Ubuntu 22.04+） 为什么切到 UEFI 就好了：Ubuntu 安装盘是按 UEFI 方式制作的。原来 BIOS（Legacy）模式下，机器用传统方式找引导 → 硬盘上有旧的、已损坏的 Legacy GRUB → 找不到 normal.mod → 进 rescue；切到 UEFI 后改用 EFI 分区引导 → 正确加载 U 盘里的 Ubuntu 安装环境 → 正常。\n核心原则：安装盘、硬盘引导、BIOS 设置三者的「模式」必须一致，否则就找不到引导。\n事后要不要改回 BIOS？——不要改。\n用 UEFI 启动安装盘，装出来的 Ubuntu 引导也是 UEFI 模式（写 EFI 分区 + grubx64.efi）。 装完又切回 Legacy → 重启用 Legacy 找引导 → 硬盘上是 UEFI 引导，找不到 → 又回到 GRUB rescue。 结论：保持 UEFI，装完也别动。 安装完成注意事项：\n全程保持 UEFI，不要中途切换模式。 让 Ubuntu 自动分区（会建 EFI 系统分区），或手动确保有 EFI 分区。 装完重启直接保持 UEFI，无需再手动选 U 盘。 若以后双系统（保留 Windows），也要保证 Windows 是 UEFI 模式，否则两系统模式打架。 二、分区与磁盘准备（安装器里的坑） 2.1 EFI 系统分区（ESP）该分多大 场景 推荐大小 说明 最小可用 100 MB 单系统勉强够，不推荐 单系统 Ubuntu 300–512 MB 能容纳多内核引导文件，升级不爆 双系统（Win+Ubuntu） 512 MB 两系统共用一个 ESP 保守上限 1 GB 再大无意义 实操要点：\n自动分区：选「清除整盘并安装」或「与 Win 共存」，安装器自动建好合适大小 EFI 分区。 手动分区：分区类型选 EFI System Partition（或挂载点 /boot/efi），格式 FAT32，大小 512 MB，放磁盘最前面最稳妥。 不要格式化成 ext4 或其它格式——必须是 FAT32，否则 UEFI 读不了。 2.2 磁盘直接格式化、无分区表（sdc/sdd 无法分区、+/- 置灰） 现象（安装器分区界面）：\n设备 显示内容 异常点 /dev/sdc ext4，挂载点 /，480 GB 是 /dev/sdc，不是 /dev/sdc1 /dev/sdd ext4，挂载点 /media/Project2，960 GB 是 /dev/sdd，不是 /dev/sdd1 原因：/dev/sdc、/dev/sdd 是整个磁盘直接被格式化为 ext4（之前直接 mkfs.ext4 /dev/sdc），没有分区表结构。安装器认为「这块盘已用完」，无法再新建分区，+/- 按钮因此置灰。\n风险提醒（动手前先确认数据）：\n操作 风险 删除 /dev/sdc 清空系统安装目标盘（预期操作） 删除 /dev/sdd 清空 Project2 数据盘，先备份 重新分区前 确认 /dev/sda 的 EFI 分区（sda1）是否为引导分区 解决方案：先用命令行 / GParted 重建分区表\n⚠️ 以下命令会清空 sdc 和 sdd 所有数据，执行前确认数据安全。\n步骤 1：打开终端（安装界面按 Ctrl + Alt + T）\n步骤 2：查看磁盘结构，确认无分区\n1 2 sudo lsblk sudo fdisk -l 确认 /dev/sdc、/dev/sdd 没有 sdc1、sdd1 这类分区。\n步骤 3：擦除并新建 GPT 分区表\n1 2 3 4 5 6 7 8 # 擦除 sdc 上的文件系统签名 sudo wipefs -a /dev/sdc # 创建 GPT 分区表 sudo parted /dev/sdc mklabel gpt # 同样处理 sdd sudo wipefs -a /dev/sdd sudo parted /dev/sdd mklabel gpt 若 wipefs 不存在，可用：\n1 2 sudo dd if=/dev/zero of=/dev/sdc bs=1M count=100 sudo dd if=/dev/zero of=/dev/sdd bs=1M count=100 步骤 4：返回安装器重新扫描\n关掉终端，回到安装器分区界面，点 Back 再 Continue 刷新，应能看到 /dev/sdc、/dev/sdd 变成 free space，+/- 按钮变亮。\n替代方案：GParted 图形化\n1 sudo gparted 右上角选 /dev/sdc → 菜单 Device → Create Partition Table → gpt → Apply。 同样处理 /dev/sdd。 关闭 GParted，回安装器。 处理完之后的分区建议\n磁盘 建议操作 /dev/sdc 新建 /dev/sdc1，全部空间，挂载点 /，ext4 /dev/sdd 新建 /dev/sdd1，全部空间，挂载点 /media/Project2，ext4 关键提醒：\nBoot loader 安装位置：若引导盘与系统盘不同（如系统装 sdc、EFI 在 sda1），建议把 boot loader 指向带 EFI 分区的那块盘，让 EFI 引导统一管理，避免多盘混乱。 不要只格式化不解分区表：核心问题是盘被直接格式化、无分区表，格式化解决不了，必须先 mklabel gpt。 三、128G 内存要不要开 swap 3.1 结论 128 GB 内存日常基本用不到 swap。但建议保留 2–4 GB swapfile 作安全阀，而不是完全不开。\n3.2 为什么基本用不到 swap 是物理内存不够时把不常用内存页换到磁盘。 128 GB 对个人开发、编译、多开、日常办公几乎不可能耗尽。 现代内核有 zram（内存里压缩 swap），进一步降低对磁盘 swap 依赖。 3.3 何时需要 swap 场景 是否需要 swap 正常开发 / 编译 / 多开浏览器 不需要，留小 swap 无妨 内存耗尽（OOM） 有 swap 先换页、慢但活着；无 swap 时内核 OOM killer 杀进程 休眠（hibernate） 需要 swap ≥ 内存，即 128 GB——太浪费，不建议 内核崩溃转储（kdump） 需预留一小块，但很小 内存压缩兜底 zram 已覆盖 关键区别：休眠需要 swap = 内存大小；防 OOM只需很小一块。128 GB 不该为休眠牺牲 128 GB 磁盘。\n3.4 推荐配置与操作 方案 大小 适用 推荐：小 swapfile 2–4 GB 安全阀，防 OOM，不占空间 完全不开 0 内存极大且从不跑爆表任务，可接受偶尔进程被杀 休眠用 = 内存 128 GB 不推荐，纯浪费 NVMe 空间 手动分区时把空间全给 /，装完用 swapfile 补一个小的：\n1 2 3 4 5 sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo \u0026#39;/swapfile none swap sw 0 0\u0026#39; | sudo tee -a /etc/fstab 验证：\n1 2 free -h swapon --show 四、安装/启动黑屏（nomodeset 救安装） 4.1 根因一句话 内核已加载、NumLock 灯有响应 = 系统没死机，只是显示无输出。本机是 18.04 默认内核（4.15 / HWE 5.4）太老，不支持新主板 + 11/12 代 Intel 核显（UHD 750 / Xe）的 KMS（内核模式设置），导致黑屏。加 nomodeset 强制退回基础显示模式即可进系统。\n4.2 术语速查 术语 说明 KMS 内核模式设置，让内核接管显卡分辨率切换；老内核 + 新核显易黑屏 nomodeset 启动参数，禁用 KMS，退回基础显示模式，绕开黑屏 HWE 内核 把新版 Ubuntu 的新内核回灌到旧 LTS，提升新硬件支持 grub\u0026gt; 按 Shift/Esc 时机不对时进入的 GRUB 命令行，不能直接编辑启动项 4.3 已验证步骤（按实际顺序） 步骤 1：安装时加 nomodeset 进安装界面 ✅ 在启动菜单操作（不要按回车）：\n方向键选中 Install Ubuntu 按 e 进入启动参数编辑 找到 linux 开头那行，行尾 quiet splash 后追加 nomodeset： ... quiet splash nomodeset --- 按 Ctrl+X 或 F10 启动 → 进入安装界面，正常安装 步骤 2：安装后重启又黑屏 → 进桌面 ✅ 重启后再次黑屏，因为 nomodeset 只加在了 U 盘那次启动，硬盘里的 GRUB 没有它。\n2.1 调出 GRUB 菜单\n开机在主板 Logo 出现时，连续点按 Esc（UEFI）（不要按住，按住会进 grub\u0026gt; 命令行）。\n2.2 误入 grub\u0026gt; 命令行时退回菜单\n若不小心进了 grub\u0026gt; 命令行，输入：\n1 normal 此时开机后仍然无法回到 GRUB 菜单，一般是需要重启电脑后，连续点击 ESC 按键，等跳出 grub 菜单的时候就停止按按键，不能一直按着。\n成功则回到 GRUB 菜单（无效再试 exit，仍不行 reboot 重来）。\n2.3 加 nomodeset 进桌面\n回到菜单后：\n选中 Ubuntu，按 e linux 行末尾加 nomodeset 按 Ctrl+X / F10 启动 → 进入桌面 步骤 3：永久写死 nomodeset（保命，防重启再黑屏）✅ 进桌面后终端执行：\n1 sudo nano /etc/default/grub 改成：\n1 GRUB_CMDLINE_LINUX_DEFAULT=\u0026#34;quiet splash nomodeset\u0026#34; 保存退出（Ctrl+O → 回车 → Ctrl+X），必须更新 GRUB：\n1 sudo update-grub 此后每次启动都带 nomodeset，不再黑屏。\n步骤 4：确认显卡与系统版本 ✅ 1 2 3 ubuntu-drivers devices # 列出核显 + WiFi，确认有无独显 sudo ubuntu-drivers autoinstall # 提示 No drivers found —— 正常，无独显可装 lsb_release -a # 确认 Ubuntu 18.04.6 LTS 4.4 未验证备选（本次未实测，存疑备查） 以下方案未在本机实际执行确认，仅作为已知可行思路保留。\n方案 B：装 OpenSSH，远程编译（最省事） ⚠️ 未验证\nnomodeset 已永久写死后，本地显示器只需能开终端，真正的重活在另一台电脑 SSH 进来做：\n1 2 3 4 5 sudo apt update sudo apt install openssh-server -y sudo systemctl enable ssh sudo systemctl start ssh ip addr show | grep inet # 记下本机 IP 18.04 已 EOL，apt update 报 404 时先换源：\n1 2 sudo sed -i \u0026#39;s|http://.*ubuntu.com|http://old-releases.ubuntu.com|g\u0026#39; /etc/apt/sources.list sudo apt update 方案 D：升级内核 + Mesa 让核显正常工作，再去掉 nomodeset ⚠️ 未验证\n若本地显示器分辨率/流畅度不满意，可升级内核 + Mesa 让新核显正常工作（存在稳定性风险，操作前务必先完成步骤 3 的 nomodeset 永久化并确认 SSH 可用，图形万一出问题也能远程救）：\n1 2 3 4 5 6 7 8 9 10 11 12 # 升级内核到 5.15 LTS sudo add-apt-repository ppa:cappelikan/ppa -y sudo apt update \u0026amp;\u0026amp; sudo apt install mainline -y # 打开 mainline 图形工具，选 5.15.x LTS 安装，重启 # 升级 Mesa 驱动 sudo add-apt-repository ppa:kisak/kisak-mesa -y sudo apt update \u0026amp;\u0026amp; sudo apt upgrade -y # 去掉 nomodeset 测试 sudo nano /etc/default/grub # 改回：GRUB_CMDLINE_LINUX_DEFAULT=\u0026#34;quiet splash\u0026#34; sudo update-grub \u0026amp;\u0026amp; sudo reboot 不黑屏 → 核显正常；又黑屏 → 改回 nomodeset 保留。 4.5 命令速查 目的 命令 验证状态 永久 nomodeset 编辑 /etc/default/grub 后 sudo update-grub ✅ 确认显卡 ubuntu-drivers devices ✅ 看系统版本 lsb_release -a ✅ 装 SSH 服务 sudo apt install openssh-server -y ⚠️ 看本机 IP ip addr show | grep inet ⚠️ 升级主线内核工具 sudo apt install mainline -y ⚠️ 升级 Mesa sudo add-apt-repository ppa:kisak/kisak-mesa -y \u0026amp;\u0026amp; sudo apt upgrade -y ⚠️ 18.04 换 EOL 源 sudo sed -i 's|http://.*ubuntu.com|http://old-releases.ubuntu.com|g' /etc/apt/sources.list ⚠️ 五、进桌面花屏（LightDM 绕过 Wayland） 5.1 30 秒判断（四者全中就是它） 现象 是否命中 开机自动进图形登录/桌面 → 花屏（撕裂、糊屏、残影、色彩错乱） ✅ Ctrl+Alt+F2~F6 切到命令行（TTY）完全正常 ✅ 命令行里执行 startx 能正常进入图形界面 ✅ 同一台硬件装 Ubuntu 22.04 无此问题 ✅ 四者全中 → 不是显卡坏了，是 Ubuntu 18.04 的 GDM3 登录界面默认跑在 Wayland 上，而老图形栈对新显卡的 Wayland 渲染支持残缺，导致花屏。绕过 Wayland（用纯 X11 的 startx 或 LightDM）即可解决。\n5.2 根因与原理 图形启动分两层：\n显示管理器（Display Manager，如 GDM3 / LightDM）：开机自动运行，负责画登录界面、把用户带进桌面。 X 会话（Xorg）：真正的图形服务，startx 直接启动它。 startx 能正常显示，说明 Xorg + 内核通用显卡驱动（modesetting）+ Mesa 用户态驱动这套组合是工作的。问题被锁定在 GDM 这一层。\n真凶：GDM3 的 Wayland 登录界面。Ubuntu 18.04 的 GDM3 默认用 Wayland 渲染登录界面（greeter）。Wayland 依赖更新的 EGL/GBM 渲染路径，而 2019 年后推出的新显卡（Intel 11 代及以后核显、AMD RX 5000 及以后、NVIDIA RTX 30/40 系），在 18.04 的老内核 4.15 + 老 Mesa 18.2 下，这条路径支持残缺 → 花屏。startx 走纯 X11，完全绕开 Wayland，所以正常。\n为什么 22.04 没事（对照）：\n组件 18.04 22.04 影响 内核 4.15（HWE 最高 5.4） 5.15+ 新显卡内核态驱动 5.8~5.16 才陆续合入 Mesa 18.2 22.0+ Intel 11/12 代核显、AMD RDNA2/3 需 Mesa 21+ GDM Wayland 路径 不成熟 成熟 新显卡 Wayland 渲染稳定 5.3 已验证方案：换成 LightDM（推荐，最稳）✅ 1 2 3 4 5 6 7 8 9 10 # 1. 若当前花屏看不清，先用 Ctrl+Alt+F2 切到 TTY 登录 # 2. 安装 LightDM（安装过程会弹对话框，选 lightdm 为默认显示管理器） sudo apt update sudo apt install lightdm # 3. 若没弹选择框，手动指定默认显示管理器： sudo dpkg-reconfigure gdm3 # 在列表里选 lightdm # 4. 重启使新的显示管理器生效（会结束当前图形会话，先存工作） sudo reboot 生效原理：LightDM 是纯 X11 的显示管理器，不提供 Wayland 登录路径，登录界面与后续桌面会话全部走 X11，正好避开 18.04 老图形栈在新显卡上出问题的 Wayland 渲染路径，与 startx 正常是同一套机制。\n验证结果（本次实测）：\n✅ 重启后登录界面正常，无花屏； ✅ 进入桌面后拖动窗口、显示正常； ✅ 分辨率正常（非 fallback 低分辨率）； ✅ 整体可用，问题解决。 5.4 未验证备选 以下方案未在当前机器上验证，仅作备选思路保留。\n方案 A：禁用 GDM 的 Wayland（改动最小） ❌ 尚未验证\n让 GDM 登录界面也走 X11，不换显示管理器本身：\n1 2 3 4 sudo nano /etc/gdm3/custom.conf # 把被注释的行打开，设为： WaylandEnable=false sudo reboot 方案 C：换 Xfce 轻量桌面（针对「登录后桌面仍花屏」） ❌ 尚未验证\n若换 LightDM 后登录界面正常、但登进 GNOME 后桌面依然花屏，说明 Mutter 合成器在老 Mesa 上渲染异常，可换更轻量的桌面：\n1 2 sudo apt install xfce4 # 注销后，在登录界面右下角齿轮图标选 \u0026#34;Xfce Session\u0026#34; 六、诊断与常用命令速查 6.1 安装 / 图形排障诊断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 1. 看显卡型号（定位根因最直接） lspci -k | grep -EA3 \u0026#39;VGA|3D|Display\u0026#39; # 2. 当前会话是不是 Wayland（Wayland 显示 \u0026#34;Type=wayland\u0026#34;） loginctl show-session \u0026#34;$(loginctl | awk \u0026#39;/seat/{print $1}\u0026#39;)\u0026#34; -p Type # 3. 当前用的是哪个显示管理器 cat /etc/X11/default-display-manager # 4. 另起一个 X 会话测试（见「五、进桌面花屏」四连判） startx -- :1 \u0026amp; # 5. 看当前 grub 启动参数里有没有 nomodeset（见「四、安装/启动黑屏」） grep nomodeset /etc/default/grub 6.2 磁盘 / 分区 / swap 速查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 查看磁盘与分区 sudo lsblk sudo fdisk -l # 擦除文件系统签名 + 建 GPT 分区表（会清数据，先备份！） sudo wipefs -a /dev/sdc sudo parted /dev/sdc mklabel gpt # 图形化分区工具 sudo gparted # 创建 swapfile（2–4 GB 即可） sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo \u0026#39;/swapfile none swap sw 0 0\u0026#39; | sudo tee -a /etc/fstab # 验证 swap free -h swapon --show 七、经验与建议 新硬件 + 老系统（18.04）问题，优先怀疑「驱动/显示路径/启动模式不兼容」，而非硬件坏了。命令行正常 + startx 正常（花屏）或 NumLock 有响应（黑屏）是关键判断依据。 安装阶段先保证「模式一致」：U 盘制作方式、BIOS 启动模式、硬盘引导方式三者统一（都 UEFI 或都 Legacy），否则进 GRUB rescue。装完别改回原模式。 黑屏 → nomodeset，花屏 → 换 LightDM，两条都是「绕开老栈不支持的新路径」，与历史成功经验一致。 整盘被直接格式化会导致安装器无法分区（+/- 置灰），本质是缺分区表，必须先 mklabel gpt 再分，光格式化没用。 Ubuntu 18.04 标准支持已于 2023 年结束，apt 安全更新已停。若这台机器是为跑老工具链（交叉编译、老 SDK），更推荐在 22.04 宿主机上用 docker run -it ubuntu:18.04 或虚拟机获得老环境——既避开图形坑，又持续获得宿主系统的安全性。 八、参数对照表（脱敏） 本文已对作者专属信息做脱敏。下表是你按自己环境需要替换的全部占位符，也是日后回看时一眼能找到的「要配置什么」：\n占位符 含义 你的取值 \u0026lt;HOST_BOARD\u0026gt; 触发黑屏/启动异常的硬件特征（示例：技嘉 Z590 + 11/12 代 Intel 核显 UHD 750/Xe） 任何「新硬件 + 老内核 4.15」组合都适用，换成你的主板/核显即可 \u0026lt;DISK_SYS\u0026gt; / \u0026lt;DISK_DATA\u0026gt; 系统盘 / 数据盘设备名（文中示例 sdc / sdd，实际以 lsblk 为准） 以你机器的实际设备名为准，勿照抄 \u0026lt;EFI_DISK\u0026gt; 承载 EFI 系统分区的磁盘（文中示例 sda，其 sda1 为 ESP） 以你机器 EFI 分区所在盘为准 说明：本篇不含内网 IP、账号、密码、公司域名等敏感信息，故脱敏表很轻。真正需要替换大量参数的场景在系列第二篇（Android 编译环境），那里有完整的脱敏对照表。\n","permalink":"https://sikinzen.github.io/posts/ubuntu1804-os-install-pitfalls/","summary":"新硬件装 Ubuntu 18.04 的四道坎：进不去 U 盘（UEFI 模式配对）、分区异常（EFI 分区 + 重建 GPT）、大内存 swap、黑屏用 nomodeset、花屏换 LightDM。按安装时间线组织，含速判表、原理、已验证步骤与未验证备选，系列第一篇。","title":"Ubuntu 18.04 装系统趟坑实录：U盘启动 + 分区 + 黑屏（nomodeset）+ 花屏（LightDM）"},{"content":"背景 做 Android 系统级开发，最让人抓狂的往往不是写代码，而是等编译。一套 Unisoc T615 / Android 16 代码树全量编译，在 20 线程的服务器上也要跑将近 4 个小时。每次改几行代码就要重编一遍，时间成本极高。\nCcache 是业界成熟的编译器缓存方案，能在「同样源码再次编译」时直接命中缓存、跳过实际编译。本文记录我在编译服务器 192.168.1.209 上配置 ccache，并用三种编译方式做对照实测的全过程，以及由此延伸出的其他加速手段。\n一句话结论：开启 ccache 后，「全量重编」从 3h49m 降到 2h18m（1.65×）；日常改文件后保留 out/ 做增量编译可进一步降到 1h38m（2.35×）。增量编译 + ccache 是当前性价比最高的两项加速手段。\n一、优化总览 模式 耗时 相对冷编 节省时间 ccache 命中 说明 A · 冷编译（基准） 3h49m02s（13,742s） 1.00× — 0% 无缓存 + 删除 out 全编 B · 热编译（ccache） 2h18m25s（8,305s） 1.65× 39.6% 100% 有缓存 + 删除 out 全编 C · 增量编译（推荐） 1h37m38s（5,858s） 2.35× 57.4% 100% 有缓存 + 保留 out + 重铺 overlay 结论很清晰：\nccache 对「全量重编」有效：C/C++ 缓存命中率 100%，整体提速 1.65×。代价是首次需要冷编填满缓存。 增量编译比「删 out + ccache」更快：保留 out/ 后，未改动模块的 .o 与链接产物直接复用，连 ccache 的 I/O 都省了，相对热编译再快约 29%。 二、Ccache 配置（已在 192.168.1.209 落地） 2.1 生效原理 本代码树原生支持 ccache：当 USE_CCACHE=1 且 CCACHE_EXEC 指向 ccache 可执行文件时，build/make/core/ccache.mk 与 build/soong/cc/config/global.go 两条路径均会用 ccache 包装 C/C++ 编译器。\n注意：ccache 仅对 C/C++（含 clang） 生效，对 Java/Kotlin 无效。Android 的 native 代码主体是 C/C++，因此收益主要体现在 native 重编阶段。\n2.2 环境变量（已写入 ~/.bashrc 与 ~/.bash_profile） 1 2 3 4 5 6 7 8 9 10 11 12 # \u0026gt;\u0026gt;\u0026gt; ccache for Android/Unisoc builds (added by WorkBuddy 2026-08-25) \u0026gt;\u0026gt;\u0026gt; export CCACHE_DIR=/media/D/ccache export CCACHE_EXEC=/usr/bin/ccache export USE_CCACHE=1 export CCACHE_MAXSIZE=100G export CCACHE_COMPRESS=1 # \u0026lt;\u0026lt;\u0026lt; ccache \u0026gt;\u0026gt;\u0026gt; # 持久化缓存参数 cd /media/D/Unisoc/T615_A16 ccache -M 100G ccache -o compression=true 完整 Android 编译的缓存可达 20~50 GB，设 100 GB 并开启压缩比较稳妥。.bashrc 仅在交互式 shell 生效，若用非交互方式触发编译，需由脚本自行 export 兜底。\n2.3 验证配置已生效 验证手段 命令 预期结果 查看配置 ccache --show-config cache_dir = /media/D/ccache、max_size = 100.0G、compression = true 查看统计 ccache -s 编译中 cache miss/hit 持续增长；热编后 cache hit rate 接近 100% 实时观察 watch -n 5 'ccache -s' 冷编见 miss 涨，热编见 hit 涨 源码确认 grep CC_WRAPPER sys/build/make/core/ccache.mk 能看到 CC_WRAPPER := $(CCACHE_EXEC) 2.4 多版本共存建议 若同一台服务器同时编译 Android 14 与 Android 16，建议按大版本拆分缓存目录，避免工具链、宏定义差异导致相互淘汰：\n1 2 3 4 5 6 7 # Android 14 export CCACHE_DIR=/media/D/ccache_a14 ccache -M 100G # Android 16 export CCACHE_DIR=/media/D/ccache_a16 ccache -M 100G 也可通过编译脚本根据工程路径自动切换 CCACHE_DIR。\n三、三种编译方式详解 三种方式的本质差异在于是否清空缓存与是否删除 out/：\n模式 前置 清理 命令 结果 A · 冷编译 ccache -C 清空缓存、ccache -z 清零统计 删除全部 5 个 out 目录 source zmake ... userdebug 24 缓存全 miss，耗时最长，作为基准 B · 热编译 保留 A 阶段填满的缓存 同样删除全部 5 个 out 目录 source zmake ... userdebug 24 缓存几乎全命中，验证 ccache 加速 C · 增量编译 保留缓存，且不删除 out 仅重铺 overlay（zgencode） source zmake ... userdebug 24 r 复用 .o 与链接产物，日常改文件最快 关键：若不清 out/ 直接二次编译，make/ninja 会因产物已存在而几乎不编译——那不是 ccache 的功劳，而是增量构建。要验证 ccache 命中，必须删 out/ 重新触发编译（即 B 模式）；而日常最快路径恰恰是故意保留 out/ 的 C 模式。\n5 个 out 目录：\n1 2 3 4 5 sys/out vnd/out vnd/out_vendor vnd/bsp/out vnd/out_odm 实测数据明细：\n模式 开始 结束 耗时 相对冷编 节省 ccache 命中 A 冷编译 12:39:04 16:28:06 3h49m02s（13,742s） 1.00× — 0%（miss 85,505） B 热编译 16:28:06 18:46:53 2h18m25s（8,305s） 1.65× 39.6% 100%（hit 85,503 / miss 2） C 增量编译 19:33:10 21:10:48 1h37m38s（5,858s） 2.35× 57.4% 100%（hit 2,216 / miss 0） B 模式命中率为 ccache 输出 100.00%，实际调用 85,505 次、命中 85,503 次、miss 2 次。C 模式因保留 out/，未改模块无需调用 ccache，故调用次数仅 2,216 次。\n四、结论与最佳实践 ccache 对全量重编有效：在「删除 out 后完整重编」场景下，C/C++ 缓存命中率 100%，整体提速 1.65×。代价是首次需要冷编填满缓存。 增量编译比「删 out + ccache」更快：保留 out/ 后，未改动模块的 .o 与链接产物直接复用，连 ccache 的 I/O 都省了，再快约 29%。 最佳实践： 日常改几文件 → 用 C 增量：source zmake ... userdebug 24 r（末尾 r = remake，跳过 cleanall、保留 out/） 切换大版本 / 彻底重编 / 怀疑产物污染 → 用 B 热编译：先删全部 5 个 out 目录，再 source zmake ... 调试单模块 → 用 mm / mma，秒级~分钟级 五、其他可进一步加速编译的方式 手段 原理 / 适用 预期收益 增量编译（保留 out/） 日常改文件后不清理，仅重编受影响模块 本测试最快（1h38m） 单模块编译 mm / mma 仅编译当前调试模块，跳过全树 调试单模块秒级~分钟级 分布式编译 distcc / icecc 多机并行分发编译任务 团队级线性扩展，需组网 远程缓存（如 Goma 类） 跨机器共享编译产物 多机首次即命中 硬件升级 内存（破 OOM）、NVMe SSD（I/O 瓶颈）、更多 CPU 核 消除瓶颈，稳定提速 构建调优 SOONG_PARALLEL_LINK_EXECUTION、ninja -j、关 sanitizer/调试符号 数分钟~数十分钟 out/ 放 tmpfs/ramdisk 内存充足时去磁盘 I/O（需大内存） I/O 密集阶段明显 精简 lunch 目标 最小产品配置，少编模块 按需裁剪 六、环境与数据说明 项 值 服务器 192.168.1.209（Ubuntu 18.04） CPU Intel i9-10900K，20 线程 内存 / Swap 31 GB 物理内存 + 33 GB Swap（原 2 GB，新增 32 GB 交换文件） 代码树 /media/D/Unisoc/T615_A16（AOSP Soong + Ninja） 编译目标 zprj/FS310L6/CF1/WA09_CD3384_HONOR_GO userdebug ccache /usr/bin/ccache 3.4.1，CCACHE_DIR=/media/D/ccache，100 GB，压缩开启，实测占用 ~10 GB 关键修复 Swap 2 GB → 33 GB，解决 soong_build 峰值 ~32 GB RSS 导致的 OOM 退出码 rc=1 三次均为打包脚本索取本 GO 配置未生成的 GSI/OTA 镜像，核心 .pac 固件正常产出，非编译失败 日志与数据来源 服务器端（最全）：/media/D/Unisoc/T615_A16/ccache_test_logs/ 含 build1_cold.log、build2_warm.log、build3_incremental.log、RESULT.txt、runner_stdout.log 时间标记：每个日志文件头部含 # START、尾部含 # END ... duration=...s 遗留事项 临时开启的 NOPASSWD sudo（/etc/sudoers.d/zz_build）与 32 GB 交换文件（/media/D/swapfile_32g）当前为临时生效，重启后失效；如需固化可写入 /etc/fstab，或按需撤回。 如需严格 rc=0，可在项目配置中关闭 GSI/OTA 打包项，或单独验证打包脚本；不影响 ccache 加速结论。 ","permalink":"https://sikinzen.github.io/posts/android-ccache-build-optimization/","summary":"Android 全量编译动辄 3 小时以上？本文在 Unisoc T615 / Android 16 服务器实测 ccache：全量重编提速 1.65×，保留 out 的增量编译提速 2.35×，并给出完整配置步骤与 8 种进阶加速手段。","title":"通过 ccache 提高 Android 编译效率的优化方案"},{"content":"把流水线脚本从 Jenkins 任务的「Pipeline script」内联框，搬进 Git 仓库、改用 Pipeline script from SCM 读取，是一次典型的 Pipeline as Code 实践。本文用我们一条真实存在的构建流水线（FM270 RK 固件每日构建）作为案例，复盘为什么要搬、怎么搬、以及中途纠正的几个误区。\n预期读者：已经会用 Jenkins 建过一两条流水线、知道 Pipeline 基本语法的同学。本文不从头讲 Jenkins 安装，重点在「内联 → SCM」这一跃迁的决策与坑。\n一、为什么要把流水线搬进 Git 最初这条流水线是把整段 pipeline { ... } 直接贴在 Jenkins 任务的「Pipeline script」输入框里（底层是 CpsFlowDefinition，脚本存 \u0026lt;script\u0026gt; 节点）。能跑，但有几个越来越明显的痛点：\n流水线逻辑是「代码」，却没进版本库。改一行 stage、调一个超时时间，要么进 Jenkins UI 手改，要么用 curl 把整份 config.xml POST 回去。没有 diff、没有 blame、改错了只能靠记忆回滚。 不可评审。同事想加个通知、改个分支，只能在 Jenkins 上改完告诉你「我改了」，没法走 PR 评审。 扩展受限。脚本藏在 UI 里，想抽公共脚本、加一个发布环节都很别扭——直到它变成仓库里的文件，才顺理成章地长出新的能力（见下文 Stage 7）。 把脚本抽成 Jenkinsfile 放进内部 Git（类似 GitHub 的 Gitea）之后，上述问题一次性解决：改动走 PR、可评审、可回滚、可 blame；也顺带解锁了多项目复用（FM270、FM256 共用同一个脚本仓库，各放子目录）和摆脱 curl 手改含中文 config.xml 的那堆坑（字符集、holdOffBuildUntilSave、409 之类）。\n本文主线是「Pipeline as Code / 版本管理」。禅道发布、多项目复用、摆脱 REST 手改是它带来的附带收益，下面会点到但不展开成主角。\n二、怎么搬：从内联到 from SCM 2.1 两种定义方式对比 Jenkins 的 Pipeline 定义有两种 definition class，记住这层对应关系就不会迷路：\n方式 definition class 脚本存放 配置要点 内联（Pipeline script） CpsFlowDefinition Jenkins 任务内 \u0026lt;script\u0026gt; 直接贴脚本 从 SCM（Pipeline script from SCM） CpsScmFlowDefinition Git 仓库里的 Jenkinsfile \u0026lt;scm\u0026gt; 指定仓库/分支/凭据 + \u0026lt;scriptPath\u0026gt; 指定文件路径 from SCM 的核心就是在任务配置里把「脚本来源」从「本任务内联」换成「去某个 Git 仓库的某个路径读 Jenkinsfile」。\n2.2 仓库布局 我们没有给每条流水线单独开仓库，而是用一个共享的脚本仓库，按任务名分子目录：\njenkins-pipelines/ # 内部 Git 仓库（类 GitHub 的 Gitea） ├── FM270_RK_Build_Pipeline/ │ ├── Jenkinsfile # 流水线定义 │ └── scripts/ │ └── publish_to_zentao.sh # 运行期调用的脚本（如发布到项目管理平台） ├── FM256_Pipeline/ │ └── Jenkinsfile └── ... 流水线里用 PIPELINE_DIR = \u0026quot;${WORKSPACE}/FM270_RK_Build_Pipeline\u0026quot; 引用本任务目录，运行期 scripts/*.sh 就靠它定位——这是「脚本进仓库」后才有的便利。\n2.3 关键配置字段 from SCM 任务里最关键的几行（脱敏示意）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 \u0026lt;definition class=\u0026#34;org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition\u0026#34; plugin=\u0026#34;workflow-cps@...\u0026#34;\u0026gt; \u0026lt;scm class=\u0026#34;hudson.plugins.git.GitSCM\u0026#34; plugin=\u0026#34;git@...\u0026#34;\u0026gt; \u0026lt;userRemoteConfigs\u0026gt; \u0026lt;hudson.plugins.git.UserRemoteConfig\u0026gt; \u0026lt;url\u0026gt;http://scm.example.com:3000/team/jenkins-pipelines.git\u0026lt;/url\u0026gt; \u0026lt;credentialsId\u0026gt;gitea-deploy\u0026lt;/credentialsId\u0026gt; \u0026lt;/hudson.plugins.git.UserRemoteConfig\u0026gt; \u0026lt;/userRemoteConfigs\u0026gt; \u0026lt;branches\u0026gt; \u0026lt;hudson.plugins.git.BranchSpec\u0026gt; \u0026lt;name\u0026gt;*/main\u0026lt;/name\u0026gt; \u0026lt;/hudson.plugins.git.BranchSpec\u0026gt; \u0026lt;/branches\u0026gt; \u0026lt;/scm\u0026gt; \u0026lt;scriptPath\u0026gt;FM270_RK_Build_Pipeline/Jenkinsfile\u0026lt;/scriptPath\u0026gt; \u0026lt;lightweight\u0026gt;true\u0026lt;/lightweight\u0026gt; \u0026lt;/definition\u0026gt; 要点：\n\u0026lt;url\u0026gt; + \u0026lt;credentialsId\u0026gt;：SCM 拉取用的 Git 地址与 Jenkins 凭据（注意这是「拉 Jenkinsfile 的凭据」，和运行时 scp 用的节点私钥是两回事，见 3.3）。 \u0026lt;scriptPath\u0026gt;：仓库内 Jenkinsfile 的相对路径，必须带任务子目录。 \u0026lt;branches\u0026gt;：分支 */main（refspec 写法）。 \u0026lt;lightweight\u0026gt;：true 表示轻量检出，见 3.2。 三、经验教训与踩坑 3.1 误区澄清：from SCM 并不会报 500 我们早期一度认为 CpsScmFlowDefinition（from SCM）在本环境「会 500、不可用」，所以一度只用内联。后来实测证明：from SCM 完全可用，现在这条每日构建流水线就是 from SCM。\n回头看，之前那些 500 的真正原因是别的：要么是 config.xml 本身的 XML 写得不合规（比如中文作业名在 Jenkins 主容器非 UTF-8 默认字符集下会抛 InvalidPathException），要么是 curl 推配置时没带 Content-Type: application/xml; charset=UTF-8。跟「是不是 from SCM」无关。\n结论：在本环境优先用 from SCM；若某次配置报 500，先查 XML 合法性与中文字符集，别急着甩锅给 SCM 定义方式。\n3.2 lightweight checkout 的边界 lightweight=true 只做稀疏检出——Jenkins 只把 Jenkinsfile 这一个文件拉下来用于解析流水线，速度快、节省空间。但它有一个边界：流水线在「解析期」（还没真正 checkout 完整仓库时）不能依赖仓库里其它文件。\n本例里 scripts/publish_to_zentao.sh 是在 运行期（stage 执行时）才调用，此时完整仓库早已检出，PIPELINE_DIR 指向的就是完整路径，所以安全。如果你打算在 Jenkinsfile 顶部（解析期）load 别的 .groovy 文件，轻量检出就会找不到——要么关掉 lightweight，要么把那些文件也走正式 SCM step 引入。\n3.3 两套凭据别混 SCM 凭据（credentialsId，如 gitea-deploy）：仅用于 Jenkins 拉取 Jenkinsfile。HTTPS 地址配一个 Jenkins 用户名/令牌凭据即可。 运行时凭据（如节点上的 scp 私钥 /home/builduser/.ssh/id_rsa_artifact）：流水线真正跑 scp 上传产物时用的，是构建节点本地文件，不走 Jenkins 凭据系统。 两者职责不同，配置时别把运行时私钥误填进 SCM 凭据，也别指望 SCM 凭据能帮你免密 scp。\n3.4 触发器 / 参数写进代码，消除配置漂移 内联时代，定时触发（cron）和参数常常靠 REST 注入进 config.xml，容易和「脚本本体」脱节（改了脚本忘了改触发器）。搬进 Jenkinsfile 后，我们把它们明确写进代码：\n1 2 3 4 5 6 7 triggers { cron(\u0026#39;TZ=Asia/Shanghai\\nH 2 * * 1-5\u0026#39;) // 上海时区，工作日 2 点附近 } parameters { booleanParam(name: \u0026#39;PUBLISH_TO_ZENTAO\u0026#39;, defaultValue: true, description: \u0026#39;编译并上传成功后，在项目管理平台创建构建记录\u0026#39;) } 这样「什么时候跑、带什么参数」和「跑什么」永远在同一个文件里，版本一致、一目了然。\n3.5 旧内联任务留作回退 重构没有「原地覆盖」旧的 FM270-RK-Build 内联任务，而是新建一个 from-SCM 的任务（放在 Daily_Build 文件夹下），旧任务保留。等新任务连续跑稳几天、确认行为一致后，再考虑弃用旧的。这是低风险迁移的基本功：新旧并存验证，而非一刀切替换。\n四、脱敏后的完整 Jenkinsfile 示例 下面是从真实仓库取出的 Jenkinsfile，已做脱敏（IP、账号、内部路径、仓库名、镜像名均替换为仿真值；真实映射见文末对照表）。它能完整反映这次重构后的形态——注意相比最初内联版，它多了 Stage 7「发布到禅道」、参数、buildDiscarder 保留策略，这些正是「脚本进仓库后易扩展」的实证。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 // FM270_RK_Build Pipeline（脱敏示例） // 部署：Pipeline script from SCM → Script Path = FM270_RK_Build_Pipeline/Jenkinsfile pipeline { agent { label \u0026#39;build-node-01\u0026#39; } parameters { booleanParam( name: \u0026#39;PUBLISH_TO_ZENTAO\u0026#39;, defaultValue: true, description: \u0026#39;编译并上传成功后，在项目管理平台(禅道)创建构建记录\u0026#39; ) } options { timeout(time: 8, unit: \u0026#39;HOURS\u0026#39;) disableConcurrentBuilds() buildDiscarder(logRotator(numToKeepStr: \u0026#39;20\u0026#39;)) } triggers { // 工作日 2 点附近（上海时区） cron(\u0026#39;TZ=Asia/Shanghai\\nH 2 * * 1-5\u0026#39;) } environment { PIPELINE_DIR = \u0026#34;${WORKSPACE}/FM270_RK_Build_Pipeline\u0026#34; SDK_DIR = \u0026#39;/srv/sdk/fm270-rv1103b\u0026#39; ZPRJ = \u0026#39;zprj/fm270/yqjy/jm2x0_cd1xxx_4k_apps/\u0026#39; SCP_USER = \u0026#39;artifact\u0026#39; SCP_HOST = \u0026#39;10.0.0.30\u0026#39; SCP_ROOT = \u0026#39;/srv/artifacts/fm270-build\u0026#39; SCP_KEY = \u0026#39;/home/builduser/.ssh/id_rsa_artifact\u0026#39; FTP_DIR = \u0026#39;fm270-build\u0026#39; } stages { stage(\u0026#39;1. 生成链接文件\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { sh \u0026#39;cd zcommon \u0026amp;\u0026amp; ./create_ln.sh \u0026amp;\u0026amp; cd ..\u0026#39; } } } stage(\u0026#39;2. 切换分支\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { sh \u0026#39;./zswitch_branch.sh xm_uni_branch_linux\u0026#39; } } } stage(\u0026#39;3. 更新代码\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { sh \u0026#39;./pull_code.sh\u0026#39; } } } stage(\u0026#39;4. 再次生成链接文件\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { sh \u0026#39;cd zcommon \u0026amp;\u0026amp; ./create_ln.sh \u0026amp;\u0026amp; cd ..\u0026#39; } } } stage(\u0026#39;5. Docker 编译\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { // 进入编译容器，去掉 -t（无 TTY），直接执行编译命令 sh \u0026#39;\u0026#39;\u0026#39; docker run --rm -i --entrypoint /bin/bash \\ --mount type=bind,source=$(pwd)/,target=/home/SDK_DIR \\ --net host \\ --user $(id -u):$(id -g) \\ --workdir /home/SDK_DIR \\ rk-build-image:v1.1 \\ -c \u0026#34;source zmake zprj/fm270/yqjy/jm2x0_cd1xxx_4k_apps/\u0026#34; \u0026#39;\u0026#39;\u0026#39; } } } stage(\u0026#39;6. 上传到产物服务器\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { script { def latest = sh(script: \u0026#39;ls -t output | head -1\u0026#39;, returnStdout: true).trim() if (!latest) { error \u0026#39;未在 output/ 下找到时间目录，编译产物可能缺失\u0026#39; } env.OUT_DIR = latest env.verName = latest.replace(\u0026#39;.zip\u0026#39;, \u0026#39;\u0026#39;) // 去掉版本目录名里的 .zip env.BUILD_NAME = env.verName } sh \u0026#39;\u0026#39;\u0026#39; R620_KEY=\u0026#34;${SCP_KEY}\u0026#34; chmod 600 \u0026#34;$R620_KEY\u0026#34; verPath=\u0026#34;${SCP_ROOT}/${verName}\u0026#34; ssh -o StrictHostKeyChecking=no -i \u0026#34;$R620_KEY\u0026#34; ${SCP_USER}@${SCP_HOST} \u0026#34;mkdir -p ${verPath}\u0026#34; if [ -d \u0026#34;output/${OUT_DIR}\u0026#34; ]; then scp -o StrictHostKeyChecking=no -i \u0026#34;$R620_KEY\u0026#34; -r \u0026#34;output/${OUT_DIR}/.\u0026#34; ${SCP_USER}@${SCP_HOST}:\u0026#34;${verPath}/\u0026#34; else scp -o StrictHostKeyChecking=no -i \u0026#34;$R620_KEY\u0026#34; -r \u0026#34;output/${OUT_DIR}\u0026#34; ${SCP_USER}@${SCP_HOST}:\u0026#34;${verPath}/\u0026#34; fi echo \u0026#34;${verName}\u0026#34; | ssh -o StrictHostKeyChecking=no -i \u0026#34;$R620_KEY\u0026#34; ${SCP_USER}@${SCP_HOST} \u0026#34;cat \u0026gt; ${SCP_ROOT}/latest.txt\u0026#34; \u0026#39;\u0026#39;\u0026#39; echo \u0026#34;OK 已上传: fm270-build/${verName}/\u0026#34; } } } stage(\u0026#39;7. 发布到项目管理平台\u0026#39;) { when { expression { return params.PUBLISH_TO_ZENTAO } } steps { script { if (!env.BUILD_NAME?.trim()) { error \u0026#39;BUILD_NAME 为空，无法发布（请确认上传阶段已产生 verName）\u0026#39; } sh \u0026#34;\u0026#34;\u0026#34; export BUILD_NAME=\u0026#39;${env.BUILD_NAME}\u0026#39; export FTP_DIR=\u0026#39;${env.FTP_DIR}\u0026#39; export CHANGES_SINCE_LAST=\u0026#39;自动构建发布（FM270_RK_Build Pipeline）\u0026#39; bash \u0026#39;${PIPELINE_DIR}/scripts/publish_to_zentao.sh\u0026#39; \u0026#34;\u0026#34;\u0026#34; } } } } post { always { emailext ( subject: \u0026#34;FM270 每日构建 ${currentBuild.currentResult}：${env.JOB_NAME} #${env.BUILD_NUMBER}\u0026#34;, body: \u0026#34;\u0026#34;\u0026#34;构建结果：${currentBuild.currentResult} 构建任务：${env.JOB_NAME} #${env.BUILD_NUMBER} 构建链接：${env.BUILD_URL} 编译产物已上传至产物服务器: fm270-build/${verName}/。 项目管理平台发布：${params.PUBLISH_TO_ZENTAO ? \u0026#39;已开启\u0026#39; : \u0026#39;未开启\u0026#39;} 详细日志请查看构建 Console Output。\u0026#34;\u0026#34;\u0026#34;, to: \u0026#39;dev1@corp.example.com, dev2@corp.example.com, dev3@corp.example.com, dev4@corp.example.com, dev5@corp.example.com, dev6@corp.example.com\u0026#39; ) } success { echo \u0026#39;构建并上传成功\u0026#39; } failure { echo \u0026#39;构建或上传失败，请查看上方各 Stage 日志\u0026#39; } } } 五、对照表与如何获取真实值 本文为安全起见对内部信息做了脱敏，下表给出「文中仿真值 → 真实环境」的对应关系，方便你自己环境落地时替换：\n文中仿真值 真实含义（请在你的环境替换） 如何获取 http://scm.example.com:3000/team/jenkins-pipelines.git 内部 Git（类 GitHub 的 Gitea）里的脚本仓库 问团队 SCM/CI 管理员，或自建 Gitea/GitLab 仓库 gitea-deploy Jenkins 中用于拉取该仓库的凭据 ID Jenkins → 凭据 → 找到对应用 HTTPS 凭据的 ID build-node-01 实际构建节点的 agent label Jenkins → 节点管理 → 看节点 label /srv/sdk/fm270-rv1103b 构建机上的 SDK 代码根目录 问固件负责人，或 repo 工作区根 zprj/fm270/yqjy/jm2x0_cd1xxx_4k_apps/ 具体产品/客户/变体的工程相对路径 你们 SDK 里的实际 zprj/... 路径 10.0.0.30 / /srv/artifacts/fm270-build 产物上传目标机与目录 问负责产物分发/门户的同事 artifact / id_rsa_artifact 上传用的 SSH 账号与节点私钥 构建节点 ~/ .ssh/ 下实际私钥文件 rk-build-image:v1.1 编译用 Docker 镜像名 你们编译容器的实际镜像 tag xm_uni_branch_linux 实际编译分支名 git branch -r 看远端分支 dev1@corp.example.com... 构建通知邮件收件人 你们团队的邮箱列表 项目管理平台(禅道) / publish_to_zentao.sh 内部项目管理平台及发布脚本 问研发PM/配置管理；脚本逻辑对接你们平台的 Open API 提示：脱敏值仅用于讲清结构与思路，不要原样拷到生产。把上表右列换成你自己的真实值即可。\n小结 把流水线从「内联框」搬进 Git（Pipeline as Code），最大的收益不是某个花哨功能，而是让构建逻辑重新变成「可版本化、可评审、可回滚」的代码。这次重构还顺手证明了两件事：\nPipeline script from SCM（CpsScmFlowDefinition）在本环境完全可用，早先的「500 不可用」是误判，真凶是 XML 合规性与中文字符集； 一旦脚本进了仓库，「加一个发布 stage、抽一个公共脚本、多项目复用同一仓库」都变得自然而然——重构后的 Stage 7「发布到禅道」就是最好的例子。 如果你的 Jenkins 里还躺着几条内联流水线，值得抽个下午把它们搬进 SCM；风险可控（新旧并存验证），回报长期。\n系列文章（AI 工具链） 本博客「AI 工具链」系列，教你把各类研发工具接入 AI 助手（悟空）：\n让 AI 管理你的代码仓库：Gitea MCP Server 接入指南 让 AI 帮你管代码评审：Gerrit 接入指南 让 AI 管理你的项目：禅道接入指南 让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南 让 AI 读懂你的企业微信：wechat-decrypt 接入指南 在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战 在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程 把 Jenkins 流水线重构为从 SCM 读取（Pipeline as Code） ","permalink":"https://sikinzen.github.io/posts/refactorjenkinsinlinetoscm/","summary":"以真实的 FM270 构建流水线 from-SCM 重构为例，讲清 Pipeline as Code 的动机、CpsScmFlowDefinition 与 CpsFlowDefinition 的区别、仓库布局与 lightweight checkout 等经验教训，并附脱敏后的完整 Jenkinsfile。","title":"把 Jenkins 流水线从内联脚本重构为从 SCM 读取：Pipeline as Code 实践与踩坑"},{"content":" ✅ 本文以\u0026quot;在远程编译机上用 Docker 编译一个嵌入式 Linux 固件，并自动上传到制品服务器\u0026quot;为真实案例整理。所有 IP、账号、密钥、路径、产品名均已脱敏，文末附对照表与获取方式。\n一、我们要解决什么问题？ 假设你有一个嵌入式项目（比如某款 RK 平台的开发板固件），编译过程很重：\n需要特定的交叉编译工具链、系统库（Ubuntu + 一堆依赖） 代码放在远程编译机（Linux 服务器）上 编译完要把产物（固件包）传到一台\u0026quot;制品服务器\u0026quot;供他人下载 如果每次都手动 SSH 上去敲命令，既累又容易出错。我们用 Jenkins 把它变成：点一下（或说一句话）→ 自动拉代码 → Docker 里编译 → 自动上传。\n本文就按这个真实流程，一步步教你搭出来。\n二、先搞懂 4 个核心概念（小白必看） 概念 大白话 本文里的角色 Job / Pipeline（任务 / 流水线） 一连串自动步骤的\u0026quot;菜谱\u0026quot; 我们的\u0026quot;固件构建\u0026quot;任务 Agent / Node（节点 / 编译机） 真正干活的机器（Jenkins 主控只调度，不编译） 远程 Linux 服务器（示例 IP 192.168.100.20） Stage（阶段） 把流程切成几段，方便看进度和日志 拉代码 / 编译 / 上传 各算一段 Docker 把\u0026quot;编译环境\u0026quot;打包成镜像，做到\u0026quot;换台机器编译结果一样\u0026quot; 在编译机里用 Docker 容器跑编译 为什么用 Docker 编译？ 直接在主机的环境编，容易遇到\u0026quot;我机器上能编，别人机器编不过\u0026quot;（环境不一致）。把工具链和依赖打进 Docker 镜像，所有人用同一个镜像，结果可复现，且不污染编译机系统。\n三、整体架构（文字版） [Jenkins 主控] ──调度──▶ [编译机 Agent 192.168.100.20] │ ├─ 拉代码（git / repo） ├─ docker run 起编译容器 │ └─ 容器内：source 编译脚本 → 出固件 └─ scp 上传 ──▶ [制品服务器 192.168.100.30] 四、步骤 1：准备编译机（Agent） 登录你的远程 Linux 编译机，确认装好：\nSSH：Jenkins 通过 SSH 连它（确保 22 端口开、能密码 / 密钥登录）。 Docker：docker --version 能看到版本。 Java 17+：⚠️ 这是大坑。Jenkins 的 agent 代理程序（remoting.jar）需要 Java 17 及以上。若系统默认是 Java 11，节点会连不上，报 UnsupportedClassVersionError。 方案 A（内网友好、最省事）：从局域网里已有的、装了 Java 21 的机器直接拷一份到编译机，例如 /opt/java21/；后面节点配置里显式指定这个 Java 路径。 方案 B（没有现成可拷的机器时，在 Ubuntu 上直接装）： Ubuntu 22.04+ / Debian 12+ 且能上公网： 1 2 3 sudo apt update sudo apt install -y openjdk-21-jdk # 装好后 Java 路径通常为 /usr/lib/jvm/java-21-openjdk-amd64/bin/java 离线 / 其他发行版：下载 Eclipse Temurin 21 的 tarball 解压即可： 1 2 3 wget https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jdk/hotspot/normal/eclipse -O jdk21.tar.gz sudo tar -xzf jdk21.tar.gz -C /opt # 路径形如 /opt/jdk-21.0.x+xx/bin/java 💡 编译机上的 agent 只需要 JRE（不需要完整 JDK），装 openjdk-21-jre-headless 或对应 JRE 包即可更省空间；装好后把绝对路径填到节点配置的 JavaPath，节点即上线。 代码目录与脚本：准备好项目代码路径（示例 /data/build/example-rk/rv1103），以及你们自己的编译辅助脚本（如生成软链的 create_ln.sh、切分支的 zswitch_branch.sh、拉代码的 pull_code.sh、进 Docker 编译的 env_setup.sh）。这些脚本名按你们项目实际来。 五、步骤 2：在 Jenkins 注册编译节点 Jenkins → 管理 Jenkins → 节点（Nodes） → 新建节点 节点名（例 build-agent-01），类型选固定节点（Permanent Agent） 关键配置： 远程根目录：编译机上的一个目录，如 /home/builduser 标签（Labels）：填 build-agent-01（流水线靠这个标签挑机器） 启动方式：选 Launch agents via SSH 主机：192.168.100.20 凭据：点\u0026quot;添加\u0026quot;→ 用户名 builduser、密码 BuildPass@123（或用 SSH 私钥） Java 路径（JavaPath）：填 /opt/java21/bin/java（务必填你拷过去的 Java21，否则用系统默认老 Java 会连不上） 保存 → 节点状态小球变绿即上线成功。 💡 标签（label）是流水线和机器的\u0026quot;接头暗号\u0026quot;：流水线说\u0026quot;我要 label=build-agent-01 的机器\u0026quot;，Jenkins 就把任务派给这台。\n六、步骤 3：准备 Docker 编译镜像 在编译机上拉取（或构建）你们的编译镜像，示例叫 rockchip-build:v1.1：\n1 2 3 docker pull registry.example.com/rockchip-build:v1.1 # 或本地构建 docker build -t rockchip-build:v1.1 -f Dockerfile.build . 这个镜像里应预装好交叉编译工具链、repo、Python 等所有编译依赖。\n💡 关于 Docker 本身的入门（安装、镜像、容器基本操作）：本文聚焦\u0026quot;Jenkins 里如何调用 Docker 完成编译\u0026quot;，不展开 Docker 基础。需要入门的同学可参考 Docker 官方文档，或关注本博客后续专门讲解 Docker 的文章。\n七、步骤 4：写流水线脚本（Jenkinsfile） 下面是脱敏后的完整流水线（Declarative Pipeline），共 6 个阶段。直接复制改值即可用。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 pipeline { agent { label \u0026#39;build-agent-01\u0026#39; } // 指定用哪台编译机 options { timeout(time: 8, unit: \u0026#39;HOURS\u0026#39;) // 最多跑 8 小时，防卡死 disableConcurrentBuilds() // 禁止同时跑两个，避免产物互相覆盖 } environment { SDK_DIR = \u0026#39;/data/build/example-rk/rv1103\u0026#39; // 代码根目录 FTP_USER = \u0026#39;artifact\u0026#39; // 制品服务器账号 FTP_HOST = \u0026#39;192.168.100.30\u0026#39; // 制品服务器 IP FTP_ROOT = \u0026#39;/srv/artifacts/example-rk_Build_Version\u0026#39; // 上传根目录 } stages { stage(\u0026#39;1. 生成链接文件\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { sh \u0026#39;cd zcommon \u0026amp;\u0026amp; ./create_ln.sh \u0026amp;\u0026amp; cd ..\u0026#39; } } } stage(\u0026#39;2. 切换分支\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { sh \u0026#39;./zswitch_branch.sh develop\u0026#39; // 换成你的分支名 } } } stage(\u0026#39;3. 更新代码\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { sh \u0026#39;./pull_code.sh\u0026#39; } } } stage(\u0026#39;4. 再次生成链接文件\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { sh \u0026#39;cd zcommon \u0026amp;\u0026amp; ./create_ln.sh \u0026amp;\u0026amp; cd ..\u0026#39; } } } stage(\u0026#39;5. Docker 编译\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { // 进 Docker 容器编译，等价于原 env_setup.sh 里 source 编译脚本 sh \u0026#39;\u0026#39;\u0026#39; docker run --rm -i --entrypoint /bin/bash \\ --mount type=bind,source=$(pwd)/,target=/home/SDK_DIR \\ --net host \\ --user $(id -u):$(id -g) \\ --workdir /home/SDK_DIR \\ rockchip-build:v1.1 \\ -c \u0026#34;source zmake zprj/example-rk/acme/ERK3568_4K_apps/\u0026#34; \u0026#39;\u0026#39;\u0026#39; } } } stage(\u0026#39;6. 上传到制品服务器\u0026#39;) { steps { dir(\u0026#34;${SDK_DIR}\u0026#34;) { script { // 找 output/ 下最新的时间目录作为本次产物 def latest = sh(script: \u0026#39;ls -t output | head -1\u0026#39;, returnStdout: true).trim() if (!latest) { error \u0026#39;未在 output/ 下找到时间目录，编译产物可能缺失\u0026#39; } env.OUT_DIR = latest } // 直接用编译机本地的私钥 scp 上传（不依赖 Jenkins 凭据） sh \u0026#39;\u0026#39;\u0026#39; KEY=/home/builduser/.ssh/id_rsa_artifact chmod 600 \u0026#34;$KEY\u0026#34; verPath=\u0026#34;${FTP_ROOT}/${OUT_DIR}\u0026#34; ssh -o StrictHostKeyChecking=no -i \u0026#34;$KEY\u0026#34; ${FTP_USER}@${FTP_HOST} \u0026#34;mkdir -p ${verPath}\u0026#34; scp -o StrictHostKeyChecking=no -i \u0026#34;$KEY\u0026#34; -r output/${OUT_DIR} ${FTP_USER}@${FTP_HOST}:${verPath}/ echo \u0026#34;${OUT_DIR}\u0026#34; | ssh -o StrictHostKeyChecking=no -i \u0026#34;$KEY\u0026#34; ${FTP_USER}@${FTP_HOST} \u0026#34;cat \u0026gt; ${FTP_ROOT}/latest.txt\u0026#34; \u0026#39;\u0026#39;\u0026#39; echo \u0026#34;OK 已上传到制品服务器: example-rk_Build_Version/${OUT_DIR}/\u0026#34; } } } } post { failure { echo \u0026#39;构建或上传失败，请查看上方各 Stage 日志\u0026#39; } } } 逐段解释（小白重点）：\nagent { label 'build-agent-01' }：声明\u0026quot;这个任务在标签为 build-agent-01 的机器上跑\u0026quot;。 dir(\u0026quot;${SDK_DIR}\u0026quot;)：把后续命令的工作目录切到代码根目录，免得每条都写全路径。 stage 1~4：你们项目的准备工作（生成软链、切分支、拉最新代码、再生成软链）。命令按你们实际脚本名改。 stage 5（Docker 编译） 几个关键参数： --rm：编完自动删容器，不留垃圾。 -i（不是 -t）：保持标准输入、可重定向日志，但不要 -t（TTY）—— 无界面的 CI 环境加 -t 会卡住不动。这是真实踩坑。 --mount ... target=/home/SDK_DIR：把编译机上的代码目录挂进容器，容器里改的东西映射到外面，产物才能拿到。 --user $(id -u):$(id -g)：用当前用户身份跑，避免产物文件变成 root 属主、后续 scp 没权限。 -c \u0026quot;source zmake ...\u0026quot;：在容器里执行你们的编译入口脚本。 stage 6（上传）：用编译机本地已放好的私钥 id_rsa_artifact 走 scp。好处是不用在 Jenkins 里建凭据，和直接在机器上 scp 一模一样。 八、步骤 5：创建 Pipeline 任务 Jenkins 首页 → 新建任务（New Item） 输入任务名（例 ExampleRK-Build）→ 选 Pipeline → 确定 往下翻到 Pipeline 配置区： 选 Pipeline script，把上面那段脚本粘进文本框；或 选 Pipeline script from SCM，填 Git 仓库地址和 Jenkinsfile 路径（推荐，便于版本管理） 保存。 九、步骤 6：一个隐藏坑 —— holdOffBuildUntilSave 如果你是用脚本 / REST 推送配置（像上一篇文章那样），任务可能会处于\u0026quot;暂时不能构建\u0026quot;的状态，手动点构建会报 409。原因是 Jenkins 有个 holdOffBuildUntilSave 标志，REST 改配置后会置位。\n两种解法（任选）：\n✅ 在 Jenkins Web 界面打开这个任务 → 随便点一下保存（Save），标志即清除； ✅ 或用脚本控制台（见上一篇文章 5.5）执行： 1 Jenkins.instance.getItemByFullName(\u0026#39;ExampleRK-Build\u0026#39;).save() 注意：在 Web 界面里手动编辑并保存脚本，不会触发这个坑；只有用 REST / API 批量推送配置时才会出现。所以如果你是自己手改流水线，直接保存就能跑。\n十、步骤 7：触发构建 \u0026amp; 看日志 手动：任务页点 Build Now（立即构建） AI / REST：curl -X POST .../job/ExampleRK-Build/build（见上一篇文章） 点构建号 → Console Output（控制台输出） 看实时日志，哪一步红了一目了然。 十一、常见错误速查 现象 原因 解决 节点小球红 / 无法连接，报 UnsupportedClassVersionError 编译机 Java 版本太低（\u0026lt; 17） 装 Java 21 并在节点配置里填 JavaPath 推送含中文配置返回 HTTP 500 Content-Type 没加 charset=UTF-8 改成 application/xml; charset=UTF-8 点构建报 409 not buildable holdOffBuildUntilSave 标志 界面点保存，或脚本控制台 save() Docker 步骤卡住不动 误加了 -t（TTY） 去掉 -t，只留 -i 上传 scp 报权限拒绝 私钥没 chmod 600 或用户不对 chmod 600 私钥；确认 --user 与 ssh 用户一致 十二、脱敏对照表 \u0026amp; 小白如何获取真实值 文中示例值 真实对应什么 小白怎么获取 jenkins.example.com:8080 你们的 Jenkins 地址 问运维 / 浏览器地址栏复制 build-agent-01 / 192.168.100.20 编译机节点名 / IP 你的编译服务器信息，问运维或 ip addr 查看 builduser / BuildPass@123 编译机 SSH 账号 / 密码 编译机管理员提供 /opt/java21/bin/java 编译机上的 Java 21 路径 在编译机上 which java 或你拷贝 Java 的目录 /data/build/example-rk/rv1103 项目代码根目录 你们项目的实际存放路径 develop 代码分支名 git branch 看你们用的分支 rockchip-build:v1.1 编译用 Docker 镜像 你们镜像仓库里的镜像名，问编译环境负责人 192.168.100.30 / artifact 制品服务器 IP / 账号 负责出包的同事 / 运维提供 /srv/artifacts/..._Build_Version 制品上传目录 制品服务器上约定的存放路径 /home/builduser/.ssh/id_rsa_artifact 编译机到制品服务器的 SSH 私钥 在编译机上 ssh-keygen 生成，并把公钥放到制品服务器 一句话总结：编译机信息问运维、分支问研发、Docker 镜像问编译环境负责人、制品服务器问出包同事。把这些值替换进脚本，再按第八~十节创建任务并构建，你就有了一条\u0026quot;点一下自动出包\u0026quot;的流水线。\n系列文章（AI 工具链） 本博客「AI 工具链」系列，教你把各类研发工具接入 AI 助手（悟空）：\n让 AI 管理你的代码仓库：Gitea MCP Server 接入指南 让 AI 帮你管代码评审：Gerrit 接入指南 让 AI 管理你的项目：禅道接入指南 让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南 让 AI 读懂你的企业微信：wechat-decrypt 接入指南 在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战 在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程 把 Jenkins 流水线重构为从 SCM 读取（Pipeline as Code） ","permalink":"https://sikinzen.github.io/posts/howtobuildjenkinscompilejob/","summary":"基于真实的内网构建任务整理：如何在 Jenkins 上搭建一条『拉代码 → Docker 编译 → 自动上传制品』的流水线，覆盖 Agent 节点注册、Java 版本坑、docker run 去 -t、holdOffBuildUntilSave 等实战要点","title":"在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程"},{"content":" ✅ 本文基于真实的内网 Jenkins 运维场景整理，所有涉及的内网 IP、账号、端口、路径均已脱敏。文末附「脱敏对照表 + 小白如何获取真实值」，照着替换即可复用。\n一、背景：为什么让 AI 帮你管 Jenkins？ Jenkins 是企业里最常见的持续集成（CI）工具——负责把\u0026quot;拉代码 → 编译 → 出包 → 上传\u0026quot;这一整套活儿自动化。但它的 Web 界面操作繁琐：改个流水线要进好几层菜单，换台编译机得在节点配置里翻半天。\n如果你有一个像 WorkBuddy（悟空）这样的 AI 助手，最理想的状态是只说一句话：\u0026ldquo;帮我改一下 XX 构建任务，编译机换成 223，编完传到 46\u0026rdquo;——不用自己点界面。\n本文就讲清楚三件事：AI 到底通过什么\u0026quot;接口\u0026quot;操作 Jenkins？原理是什么？能让你干哪些事？\n二、核心原理：用的不是\u0026quot;连接器\u0026quot;，也不是\u0026quot;官方 CLI\u0026quot;，而是 REST API 这是很多人（包括最初的我）会搞混的地方。先给结论：\nWorkBuddy 目前没有内置的 Jenkins 连接器（Connector / MCP）。我操作 Jenkins，用的是 Jenkins 自带的 REST API，通过 AI 的 Bash 工具直接发 curl 命令完成。\n2.1 Jenkins 的三种\u0026quot;被操控\u0026quot;方式 方式 是什么 需要装什么 适合谁 REST API（本文用的） Jenkins 把每个资源（任务、节点、构建）都暴露成一个 URL，配置是一份 XML 文档，可用 HTTP 增删改查 什么都不用装，只要能访问 Jenkins 的 HTTP 端口 任何能发 HTTP 请求的工具 / AI Jenkins CLI 官方提供的 jenkins-cli.jar，用 Java 跑命令行 需下载 jar 包，且 Jenkins 要开对应端口或走 SSH 写批量 shell 脚本的运维 连接器 / MCP WorkBuddy 这类 AI 平台的\u0026quot;即插即用\u0026quot;集成 平台提供对应连接器 最省心，但目前 Jenkins 还没有这东西 2.2 什么是\u0026quot;REST API\u0026quot;？用大白话讲 把 Jenkins 想成一家\u0026quot;网上银行\u0026quot;：\n每个任务（Job）就像你的一个账户，有专属网址，例如 http://jenkins.example.com:8080/job/你的任务名/ 这个账户的**设置（配置）**是一份 XML 文件，打开 .../config.xml 就能看到它的全部内容 你想改设置：把新 XML 用 HTTP POST 发回去 你想触发构建：访问 .../build 这个网址（POST 一下） 这一切只需要\u0026quot;能上网 + 有账号密码\u0026quot;，不需要在 Jenkins 上装任何插件 所以 AI 做的，本质上就是：替你拼好这些 HTTP 请求，并用 curl 发出去。比如\u0026quot;把编译机从 A 换成 B\u0026quot;，AI 实际干的是：拉下 config.xml → 改里面的 label → 再 POST 回去。你看到的是一句话，背后是几十条 HTTP 调用。\n2.3 为什么选 REST API 而不是 CLI？ 当时有几个现实约束：\nWorkBuddy 没有 Jenkins 连接器，MCP 路线暂时走不通； 不想在 Jenkins 服务器上额外装 CLI jar、开端口（增加运维负担和安全暴露面）； REST API 零部署，AI 在本机用 curl 就能调，最快最稳。 三、前置条件（脱敏值） 要让 AI 通过 REST API 操作 Jenkins，你需要准备：\n项目 示例值（脱敏） 说明 Jenkins 地址 http://jenkins.example.com:8080 浏览器里打开的那个地址 账号 jenkinsadmin 对目标任务有读写权限的账号 密码 / API Token Jenkins@2024（示例） 建议用 API Token，见下文 网络 AI 机器能直连 Jenkins 的 8080 端口 若中间有代理（如 Clash），需让请求绕过代理 ⚠️ 关于代理：AI 所在机器常开着系统代理（如 Clash 的 127.0.0.1:7890）。访问内网 Jenkins 时，代理会拦截请求导致连不上。解决：在 curl 加 --noproxy '*'（表示所有地址都不走代理），或把内网网段加入代理白名单。\n四、如何获取 Jenkins 凭据（小白实操） 账号密码：就是你平时登录 Jenkins Web 界面的账号密码。\n更推荐用 API Token（不用暴露登录密码，且可随时吊销）：\n浏览器登录 Jenkins 右上角点你的用户名 → Settings（设置） 左侧 API Token → Add new Token → 取个名字 → 生成 复制这串 token，它就是你的\u0026quot;密码替代品\u0026quot; 之后 curl -u 用户名:令牌 即可，和用密码一模一样。\n五、AI 能替你做的核心操作（带脱敏命令） 下面所有命令中的 \u0026lt;...\u0026gt; 都是占位，换成你自己的真实值（见文末对照表）。\n5.1 读取一个任务的配置 1 2 curl -s -u \u0026lt;JENKINS_USER\u0026gt;:\u0026lt;JENKINS_PASS\u0026gt; \\ \u0026#34;http://jenkins.example.com:8080/job/\u0026lt;JOB_NAME\u0026gt;/config.xml\u0026#34; 用途：先看清楚任务现在长什么样（编译机是哪个、上传到哪），再决定怎么改。\n5.2 修改任务配置（最关键的一步） 先把改好的配置存成 new_config.xml，再推送：\n1 2 3 4 curl -s -u \u0026lt;JENKINS_USER\u0026gt;:\u0026lt;JENKINS_PASS\u0026gt; \\ -H \u0026#34;Content-Type: application/xml; charset=UTF-8\u0026#34; \\ --data-binary @new_config.xml \\ \u0026#34;http://jenkins.example.com:8080/job/\u0026lt;JOB_NAME\u0026gt;/config.xml\u0026#34; 🔥 大坑提醒：如果配置里含中文（比如 stage 名字叫\u0026quot;Docker 编译\u0026quot;），Content-Type 必须写成 application/xml; charset=UTF-8。只写 application/xml 会让 Jenkins 按老编码（ISO-8859-1）误读中文，结果返回 HTTP 500 推送失败。这是真实踩过的坑。\n5.3 触发一次构建 1 2 curl -s -u \u0026lt;JENKINS_USER\u0026gt;:\u0026lt;JENKINS_PASS\u0026gt; \\ -X POST \u0026#34;http://jenkins.example.com:8080/job/\u0026lt;JOB_NAME\u0026gt;/build\u0026#34; 如果有参数（如选分支），改成 /buildWithParameters?branch=xxx。\n5.4 管理编译节点（Agent） 节点的配置也是 XML，地址在 computer/\u0026lt;节点名\u0026gt;/ 下：\n1 2 3 4 5 6 7 8 9 # 读取节点配置 curl -s -u \u0026lt;JENKINS_USER\u0026gt;:\u0026lt;JENKINS_PASS\u0026gt; \\ \u0026#34;http://jenkins.example.com:8080/computer/\u0026lt;NODE_NAME\u0026gt;/config.xml\u0026#34; # 修改节点配置（如换 Java 路径、换 IP） curl -s -u \u0026lt;JENKINS_USER\u0026gt;:\u0026lt;JENKINS_PASS\u0026gt; \\ -H \u0026#34;Content-Type: application/xml; charset=UTF-8\u0026#34; \\ --data-binary @new_node.xml \\ \u0026#34;http://jenkins.example.com:8080/computer/\u0026lt;NODE_NAME\u0026gt;/config.xml\u0026#34; 5.5 跑一段 Groovy 脚本（脚本控制台） Jenkins 有个\u0026quot;脚本控制台\u0026quot;，能直接执行 Groovy 代码，用来做界面做不到的底层操作。例如清掉\u0026quot;配置未保存不能构建\u0026quot;的标志：\n1 2 3 curl -s -u \u0026lt;JENKINS_USER\u0026gt;:\u0026lt;JENKINS_PASS\u0026gt; \\ --data-urlencode \u0026#34;script=Jenkins.instance.getItemByFullName(\u0026#39;\u0026lt;JOB_NAME\u0026gt;\u0026#39;).save()\u0026#34; \\ \u0026#34;http://jenkins.example.com:8080/script\u0026#34; 5.6 关于 CSRF / Crumb（防跨站请求） 如果 Jenkins 开了\u0026quot;防止跨站请求伪造\u0026quot;，某些操作可能要求带一个 Jenkins-Crumb 请求头。但用账号密码（Basic Auth）调 REST API 时，Jenkins 通常免 crumb。只有当用\u0026quot;登录 cookie + 表单\u0026quot;方式时才必须带。本文命令均按 Basic Auth 写法，一般无需 crumb。\n六、这套方式能做什么 / 不能做什么 能做：\n✅ 读取、修改任意任务的配置（编译机、步骤、上传目标等） ✅ 新建 / 删除 / 修改编译节点（Agent） ✅ 触发构建、查询构建状态与日志 ✅ 通过脚本控制台执行 Groovy，做底层维护 不能做 / 不擅长：\n❌ 像\u0026quot;连接器 / MCP\u0026quot;那样用自然语言直接对话式操作（目前是 AI 替你拼 HTTP 命令） ❌ 实时流式追日志（REST 是轮询式，要看实时日志还是得进 Web Console） ❌ 不需要你给 Jenkins 装任何东西——这是优点也是局限（功能完全取决于 Jenkins 原生 API 暴露了什么） 七、更好的演进方向 Pipeline as Code（流水线即代码）：把流水线脚本（Jenkinsfile）存进 Git 仓库，Jenkins 直接从代码库拉取执行。比手工 POST 配置更规范、可版本化、可 review。 Jenkinsfile 语法校验：本地用 jenkins-cli 或 Blue Ocean 校验。 未来若有 Jenkins MCP 连接器：那时就能像\u0026quot;连禅道、连 Gitea\u0026quot;一样，直接跟 AI 说人话操作 Jenkins，体验最佳。在那之前，REST API 是最稳的桥。 八、脱敏对照表 \u0026amp; 小白如何获取真实值 本文所有内网信息均为仿真示例值，与真实环境无关。请按下表把 \u0026lt;...\u0026gt; 换成你自己的值。\n文中示例值 真实对应什么 小白怎么获取 jenkins.example.com:8080 你们公司的 Jenkins 访问地址 问运维，或在浏览器地址栏复制你平时登录 Jenkins 的网址 jenkinsadmin / Jenkins@2024 你的 Jenkins 登录账号 / 密码或 API Token 账号 = 你登录 Jenkins 的用户名；密码 = 登录密码，或按第四节生成 API Token \u0026lt;JOB_NAME\u0026gt; 你要操作的具体任务名 Jenkins 首页任务列表里的名字 \u0026lt;NODE_NAME\u0026gt; 编译节点名 Jenkins → 管理 → 节点列表里的名字 --noproxy '*' 绕过系统代理 仅当你的 AI 机器开着代理且连不上内网 Jenkins 时才需要；否则可省略 一句话总结获取路径：Jenkins 地址和任务名在 Web 界面一眼可见；账号密码用你现有的登录凭据或生成 API Token；节点名在\u0026quot;节点管理\u0026quot;页。把这些都告诉 AI，它就能用上面的命令帮你批量改配置、触发构建。\n系列文章（AI 工具链） 本博客「AI 工具链」系列，教你把各类研发工具接入 AI 助手（悟空）：\n让 AI 管理你的代码仓库：Gitea MCP Server 接入指南 让 AI 帮你管代码评审：Gerrit 接入指南 让 AI 管理你的项目：禅道接入指南 让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南 让 AI 读懂你的企业微信：wechat-decrypt 接入指南 在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战 在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程 把 Jenkins 流水线重构为从 SCM 读取（Pipeline as Code） ","permalink":"https://sikinzen.github.io/posts/howtoconnectjenkinsandai/","summary":"记录用 WorkBuddy 操作 Jenkins 的完整原理：没有内置连接器，改用 Jenkins REST API + curl 直接调用，覆盖配置读写、节点管理、触发构建、脚本控制台，以及中文配置推送 500、CSRF 等注意事项","title":"在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战"},{"content":"背景 公司放了台 Windows 11 台式机，希望下班关机后，人在办公室另一台电脑（或家里）能随时把它唤醒，省得专门跑过去按电源键。Wake on LAN（WoL，网络唤醒） 就是干这个的。\n本文记录把「被唤醒端」配好的完整过程，以及今天实测中踩到的一个反直觉的坑。结论先行：Ubuntu 唤醒 Ubuntu 简单可靠；Windows 11 当被唤醒端配置不复杂；但当前环境下，即便两台都接在公司有线局域网，Windows 仍然无法直接唤醒 Windows——这不是目标配置错，而是发送端的网络位置（广播域/VLAN）问题，与「有线还是无线」无关。\n一、WoL 工作原理（30 秒版） 唤醒端向局域网广播一个「魔术包」（Magic Packet）：6 个 0xFF + 目标 MAC 重复 16 次，共 102 字节，UDP 发往 255.255.255.255:9（或 :7）。 目标机网卡在关机/睡眠态仍保持微弱供电并监听该包，匹配到自己 MAC 就触发开机。 三个前提： 目标 BIOS/网卡支持并开启 WoL； 发送端与目标在同一广播域（同网段；广播包不跨路由器）； 最好用有线网卡（无线从完全关机唤醒支持差，且高度依赖具体网卡）。 二、Ubuntu → Ubuntu（已验证） 唤醒端安装工具：\n1 sudo apt install wakeonlan 被唤醒端：\nBIOS 开启 Wake on LAN。 查网卡名与 MAC： 1 ifconfig # 找到网卡名（如 enp97s0f0）和 MAC 安装并查看/开启 WoL： 1 2 3 sudo apt install ethtool sudo ethtool enp97s0f0 # 看 Wake-on: g=已开, d=未开 sudo ethtool -s enp97s0f0 wol g 持久化（部分机器重启会丢 WoL 状态）：把 ethtool -s \u0026lt;网卡\u0026gt; wol g 写进 /etc/rc.local 并 chmod +x，再启用 rc-local 服务。 唤醒：\n1 wakeonlan 00:1A:2B:3C:4D:5E 这一步我实测可用。\n三、让 Windows 11 成为「被唤醒端」 Win11 没有 rc.local，配置落在 BIOS + 设备管理器 + 电源选项三处。\n3.1 BIOS/UEFI（必做） 开机进 BIOS，启用 Wake on LAN / PCI-E Power On / Wake from S5；关键：关闭 ErP Ready（欧盟节能选项，会在关机态切断给网卡的 +5VSB 待机供电，直接废掉 WoL）。\n3.2 设备管理器配置网卡 设备管理器 → 网络适配器 → 右键你的有线网卡 → 属性：\n电源管理：✅ 允许此设备唤醒计算机 + ✅ 只允许幻数据包唤醒（防被普通流量误唤醒） 高级：启用 Wake on Magic Packet（魔术包唤醒）；关闭 Green Ethernet / Energy Efficient Ethernet（节能会让网卡周期性掉电，错过魔术包） 3.3 关闭「快速启动」（Windows 特有坑，必做） 控制面板 → 电源选项 → 选择电源按钮的功能 → 更改当前不可用的设置 → 取消勾选「启用快速启动」。\n原因：快速启动本质是「混合关机」——把内核 hibern 到 hiberfil.sys，机器实际处于休眠而非真正的 S5。WoL 的 S5 唤醒事件无法在这种状态下触发。这是 Windows 上最常见的失效原因。\n3.4 取 MAC 并验证 1 2 ipconfig /all # 看「物理地址」 powercfg /devicequery wake_armed # 列表里应出现你的有线网卡 四、从 Windows 发送唤醒包（PowerShell） 唤醒端不需要 ethtool，用一段 PowerShell 发魔术包即可。把下面内容存成 wol.ps1：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # WakeOnLan magic packet sender (pure ASCII, UTF-8 safe) function Send-WOL { param( [Parameter(Mandatory=$true)] [string]$Mac, [string]$Broadcast = \u0026#34;255.255.255.255\u0026#34;, [int]$Port = 9 ) $clean = $Mac -replace \u0026#39;[-:]\u0026#39;, \u0026#39;\u0026#39; if ($clean.Length -ne 12) { throw \u0026#34;MAC \u0026#39;$Mac\u0026#39; invalid\u0026#34; } $macBytes = [byte[]]::new(6) for ($i = 0; $i -lt 6; $i++) { $macBytes[$i] = [System.Convert]::ToByte($clean.Substring($i*2,2),16) } $packet = [byte[]]::new(102) for ($i = 0; $i -lt 6; $i++) { $packet[$i] = 0xFF } for ($i = 1; $i -le 16; $i++) { $macBytes.CopyTo($packet, $i*6) } $ip = [System.Net.IPAddress]::Parse($Broadcast) $ep = New-Object System.Net.IPEndPoint($ip, $Port) $client = New-Object System.Net.Sockets.UdpClient $client.EnableBroadcast = $true $sent = $client.Send($packet, $packet.Length, $ep) $client.Close() Write-Host \u0026#34;Sent magic packet ($sent bytes) to $Mac\u0026#34; } # 用法：把 MAC 换成目标机 Send-WOL -Mac \u0026#34;00:1A:2B:3C:4D:5E\u0026#34; 运行：\n1 powershell -ExecutionPolicy Bypass -File \u0026#34;路径\\wol.ps1\u0026#34; 坑 1（脚本编码）：务必用 VS Code 以 UTF-8 保存这个 .ps1。别用 Git Bash 的 cat \u0026gt; / heredoc 重定向去写——中文或特殊字符会被存成乱码，导致文件变成「二进制」、PowerShell 一解析就报满屏 UnexpectedToken。脚本刻意写成纯 ASCII，就是从根上规避这点。\n坑 2（UdpClient 写法）：早期我用 new UdpClient(\u0026quot;255.255.255.255\u0026quot;, 9) 的构造函数写法，部分 Windows 会抛 requested address is not valid。改成「先建 UdpClient → 设 EnableBroadcast=$true → 再用 Send(bytes, len, IPEndPoint) 发往广播地址」就稳了。\n五、实测发现的关键限制：同为公司有线局域网，Windows 仍无法直接唤醒 Windows ⚠️ 这是今天最重要的结论，单独强调。期间有过一次误判，这里一并更正。\n实测经过 我在局域网内一台 Windows（连接公司有线网络） 上运行上面的脚本，目标是同属公司有线局域网的 Windows 11（完全关机态）——脚本不报错，但叫不醒。\n排查后，换成同局域网的一台 Ubuntu（同样连接公司有线网络） 执行：\n1 wakeonlan 00:E0:4C:69:6D:E2 一次成功唤醒。\n说明什么 目标机 WoL 配置完全正确（BIOS S5 唤醒、有线网卡 armed、ErP 关闭都对），MAC 也对； 问题不在目标，而在发送端能不能把广播包送到目标所在广播域。 误区纠正：和「有线 / 无线」无关 一开始我误判为「Windows 走 Wi-Fi、目标走有线，被 VLAN 隔开」。但用户确认两台都是公司有线网络，仍然唤不醒——这直接推翻了「无线 vs 有线」的解释：\n失败与介质无关。两台都走有线，依然失败； 真正的变量仍然是「发送端所处的广播域 / VLAN」，只是这次它不以「无线 / 有线」的面目出现。 更可能的根因：802.1X 动态 VLAN 两者都接「公司有线局域网」≠ 两者在同一广播域。公司网络普遍启用 802.1X 认证 + 动态 VLAN 分配：不同机器（即便都插网线）按账号 / 证书 / 策略可能被划入不同 VLAN，彼此不在同一广播域。Ubuntu 恰巧和目标落在同一 VLAN，广播直达；而那台 Windows 被分到另一个 VLAN，广播到不了目标。\n这也解释了「不报错却唤不醒」：脚本确实把包发出去了，但包被 VLAN / 交换机挡在发送端自己的广播域里，根本没走到目标网卡。\n仍未 100% 排除的 Windows 侧因素 即便如此，也不应完全排除 Windows 发送端本身的行为差异：\n有限广播 vs 定向广播：脚本发往 255.255.255.255（受限广播），某些 Windows / 网络组合下不会真正出网卡；而 wakeonlan 的实现可能用了定向广播或行为不同。可尝试把脚本广播地址改成目标子网定向广播（如 192.168.x.255）。 网络位置 / 防火墙：若 Windows 把网络识别为「公用」，可能对出站广播更保守。 当前可落地的结论 目前 Windows（同公司有线局域网）还无法直接唤醒 Windows，而同广播域的 Ubuntu 可以。 根因是发送端与目标不在同一广播域（公司 802.1X / VLAN 分配所致），与「有线还是无线」无关。\n可行的替代方案：\n用与目标同广播域的机器当唤醒端（如那台 Ubuntu，已验证可用）； 若坚持从 Windows 发，需确认该 Windows 与目标在同一 VLAN / 网段（对比两边 ipconfig 的 IP 前三段与子网）； 必须跨 VLAN 唤醒时，需要 WoL 中继（让同广播域设备转发魔术包）或路由器做 UDP 9 端口转发 / 定向广播。 顺带纠正一个我中途的误判：曾以为 WoL 开错了网卡、又以为是有线 / 无线隔离；两次都被事实修正。Ubuntu 能用有线 MAC 唤醒，证明目标端一直没问题；真正的变量始终是「发送端网络位置（广播域）」。\n六、排错清单 BIOS 开了 Wake on LAN 且关了 ErP？ 设备管理器里对应联网网卡的电源管理两项已勾？ 快速启动已关？ powercfg /devicequery wake_armed 能看到目标网卡？ 发送端与目标同一广播域/VLAN？（公司 802.1X 即便都插网线也可能分属不同 VLAN，是头号元凶，与有线无线无关） 用的是有线 MAC？ipconfig /all 里带 IP 的那张才是活动网卡 先测「睡眠」再测「完全关机」，缩小是 Windows 配置还是 BIOS S5 的问题 七、小结 Ubuntu ↔ Ubuntu 的 WoL 简单可靠，已验证； Windows 11 当被唤醒端，重点在 BIOS（开 WoL、关 ErP）+ 设备管理器（武装有线网卡）+ 关快速启动； 唤醒脚本用 PowerShell 即可，注意脚本文件编码与 UdpClient 的广播写法两个坑； 最大的坑不是配置，而是网络位置（广播域/VLAN）：即便两台都接公司有线局域网，Windows 发往 Windows 的唤醒包仍可能石沉大海（常被 802.1X/VLAN 隔开）；此时让同广播域的机器（如 Ubuntu）来发最稳。 本文基于 2026-07-13 的真实配置与实测过程整理。MAC 地址在文中以占位符 00:1A:2B:3C:4D:5E 表示，实际使用时请替换为你目标机的真实物理地址。\n","permalink":"https://sikinzen.github.io/posts/wakeonlanguide/","summary":"Wake on LAN 配置全记录：Ubuntu↔Ubuntu 已跑通，Windows 11 被唤醒端配置 + PowerShell 唤醒脚本；重点揭示即便两台均为公司有线局域网，Windows 仍无法直接唤醒 Windows（广播域/VLAN 隔离，与有线无线无关），以及 .ps1 脚本编码陷阱。","title":"远程唤醒实战：把 Ubuntu 与 Windows 11 配置成可被局域网唤醒的电脑"},{"content":"这篇会持续记录日常开发中踩到、又反复需要翻查的软件使用技巧。不追求大而全，只收真正常用的。\nGit 让 git status 显示中文文件名 默认情况下，Git 会对非 ASCII 文件名（如中文）做八进制转义显示。在 PowerShell 或 Git Bash 里跑 git status，中文目录和文件名会变成一串难读的转义码：\n1 modified: \u0026#34;\\344\\270\\252\\344\\272\\272\\347\\254\\224\\350\\256\\260/...\u0026#34; 原因：Git 的 core.quotepath 选项默认开启，对非 ASCII 路径进行 quote 转义，导致中文显示为八进制序列。\n解决：关闭该选项，Git 即可直接输出原始 UTF-8 中文：\n1 2 3 4 5 # 仅对当前仓库生效 git config core.quotepath false # 或全局生效（推荐，一次解决所有仓库） git config --global core.quotepath false 设置后，同样的 git status 输出变为：\n1 modified: 个人笔记/个人理财/经济学入门.md 中文文件名正常显示。\n用 Syncthing 同步 Git 仓库时的冲突副本 如果像我一样用 Syncthing 等同步工具在多设备间同步 Git 仓库，仓库根目录可能冒出 .sync-conflict-2026xxxx-xxxxxx-XXXXXX.gitignore 这样的冲突副本。这是同步工具在两端都检测到改动、无法自动合并时生成的保留文件。处理方式是先用 diff 比对两份内容，确认当前 .gitignore 已是完整最新版本后，直接删除冲突文件即可，无需手动合并。\nGerrit 通过 SSH 克隆仓库报 Permission denied (publickey) 使用 Gerrit 这类代码评审平台时，常会直接复制这样的克隆命令：\n1 git clone ssh://\u0026lt;gerrit-host\u0026gt;:29418/\u0026lt;repo-name\u0026gt; 如果照搬，很容易遇到如下报错：\n1 2 3 4 \u0026lt;当前系统登录名\u0026gt;@\u0026lt;gerrit-host\u0026gt;: Permission denied (publickey). fatal: 无法读取远程仓库。 请确认您有正确的访问权限并且仓库存在。 原因：SSH 地址里没有指定用户名时，SSH 客户端会默认使用当前操作系统的登录用户名去尝试认证。但 Gerrit 的 SSH 账号名是你在 Gerrit Web 上注册的账号（通常是邮箱前缀或工号），与 Linux 主机的登录名往往不是一回事。用错用户名去撞认证，自然每次都被拒绝，报 publickey 错误。\n解决：在 SSH 地址中显式带上 Gerrit 账号名即可：\n1 git clone ssh://\u0026lt;your-gerrit-username\u0026gt;@\u0026lt;gerrit-host\u0026gt;:29418/\u0026lt;repo-name\u0026gt; 指定正确用户名后，认证就能通过，克隆正常进行。\n补充排查：如果带上正确用户名后仍报 publickey 错误，则问题大概率在本机公钥未登记到 Gerrit。按以下顺序检查：\n本地生成密钥（若尚无）：ssh-keygen -t rsa -b 4096 -C \u0026quot;your-email@example.com\u0026quot; 取出公钥内容：cat ~/.ssh/id_rsa.pub，复制整行 登录 Gerrit Web → Settings → SSH Keys，粘贴并添加 验证连通性： 1 ssh -p 29418 \u0026lt;your-gerrit-username\u0026gt;@\u0026lt;gerrit-host\u0026gt; gerrit version 返回 gerrit version X.Y.Z 即表示认证成功，之后 clone / push 都不会再被拦。\n提示：Gerrit 默认 SSH 端口是 29418（区别于标准 22 端口），地址里务必带对端口号。\nRepo（Android 编译工具） repo init 报 GPG「无法检查签名：没有公钥」 在内部编译服务器（如 192.168.1.47:29418 上的 Rockchip / RV1103B 系 SDK）执行 repo init 时，常卡在 git-repo 自身的标签校验，报类似：\n1 2 3 gpg: 签名建立于 2013年06月24日 星期一 18时04分20秒 CST gpg: 使用 RSA 密钥 C0E62B258BA24D13 gpg: 无法检查签名：没有公钥 原因：repo 启动器在初始化时会校验它自身（git-repo 代码）的发布标签 GPG 签名。这个 SDK 的 repo 标签由厂商内部账号 build@wiselinksz.com 用密钥 C0E62B258BA24D13 签名，而本机 GPG 密钥环里没有这把公钥，于是校验中断、init 失败。\n注意：报错里的 C0E62B258BA24D13 是 repo 工具自身的标签签名密钥，和你的 Gerrit SSH 密钥不是一回事，别混淆。\n解决\n最快、已采纳方案（推荐）：给 repo init 加 --no-repo-verify，跳过 repo 工具自身发布标签的签名校验。它只关掉供应链自检，不影响业务代码拉取和后续 repo sync。\n1 2 3 4 ../../remote_build/FM270/RV1103B/.repo/repo/repo init \\ -u ssh://sjinqian@192.168.1.47:29418/manifest.git \\ -m EVB-RV1103B-SDK.xml \\ --no-repo-verify 更严谨的正解：把公钥导入 GPG 密钥环，让校验能通过：\n1 2 3 gpg --recv-keys C0E62B258BA24D13 # 需能访问公钥服务器 # 或拿到厂商提供的 key 文件后： gpg --import \u0026lt;keyfile\u0026gt; 但在内网友好环境、且厂商公钥未公开的情况下，--recv-keys 往往取不到，所以实际采用「跳过校验」。\n已固化为惯例：后续在这些编译服务器（gpu45 192.168.1.45、223 hasee 等）执行 repo init 一律带 --no-repo-verify。\n补充：不同机器自带的 repo 启动器版本不一致也曾引发别的问题（如 gpu45 上老 repo 用 Python2 语法直接 SyntaxError）。长期建议各机改用一份干净的官方 repo 启动器，并固定 REPO_REV（如 stable），避免再踩版本坑。\nPowerShell / 终端 解决 PowerShell 红字蓝底看不清 在经典 PowerShell（非 Windows Terminal）中，git status 的 modified 文件默认用红色显示，而 PowerShell 的默认背景是深蓝色（#012456）。红字配蓝底，对比度极差，几乎没法读。\n有以下四种解法，按推荐程度排序：\n方案一（推荐）：改用 Windows Terminal\nWindows 10/11 自带，按 Win + R 输入 wt 回车即可打开。默认黑色背景，红色文字清晰可辨，还支持多标签和自定义主题。把快捷方式固定到任务栏，以后默认用它替代 PowerShell 即可。\n方案二（永久）：修改 PowerShell 默认配色\n右键点击 PowerShell 窗口标题栏 → 属性 → 颜色 → 选中「屏幕背景」→ 改为黑色 → 确定时选择「修改启动该窗口的快捷方式」。这样以后每次打开都会生效。\n方案三（最快）：临时改当前会话背景色\n1 2 $Host.UI.RawUI.BackgroundColor = \u0026#34;Black\u0026#34; Clear-Host 输入后背景立即变黑，但只影响当前窗口，关闭即失效。\n方案四（Git 层面）：修改 Git 输出颜色\n让 Git 把 modified 文件从红色改为黄色，在蓝底上也能看清：\n1 2 git config --global color.status.changed \u0026#34;yellow normal\u0026#34; git config --global color.status.untracked \u0026#34;cyan normal\u0026#34; 此方案治标不治本——其他命令行工具的红字蓝底问题依旧存在。\n小结：Windows Terminal 或把 PowerShell 背景改成黑色是从根本上解决问题；改 Git 颜色只是绕开显示问题。\nCursor 在 Cursor 中接入 DeepSeek API Cursor 允许把任意「OpenAI 兼容」的接口注册为自定义模型。DeepSeek 的 API 在设计上就兼容 OpenAI / Anthropic 的请求格式，因此无需任何代理或转发层，直接填入参数即可在 Cursor 里用上 DeepSeek。\n核心参数\n参数 值 base_url（OpenAI 格式） https://api.deepseek.com base_url（Anthropic 格式） https://api.deepseek.com/anthropic api_key 到 DeepSeek 开放平台申请 model deepseek-v4-flash / deepseek-v4-pro 在 Cursor 里配置\n打开 Cursor → Settings（Ctrl + ,）→ 搜索 Model 进入 Models 设置 找到自定义模型 / Add Custom Model（或切换到 OpenAI-compatible 提供商） 填入上述参数： Base URL：https://api.deepseek.com API Key：你的 DeepSeek Key Model Name：deepseek-v4-flash（或 deepseek-v4-pro） 保存后，在模型下拉列表里即可选中 DeepSeek 进行对话 / 补全 模型选择的注意事项\ndeepseek-chat 与 deepseek-reasoner 计划于 2026/07/24 弃用。新接入请直接用 deepseek-v4-flash 或 deepseek-v4-pro，避免临近弃用期还要改配置。 deepseek-v4-flash：响应快、成本低，适合日常代码补全与轻量问答。 deepseek-v4-pro：能力更强，适合复杂推理、长上下文任务。 补充：用 OpenAI SDK 直接调用（首次联调）\n如果想在脚本里验证 Key 是否可用，或把 DeepSeek 接进自己的工具链，直接用 OpenAI 官方 SDK 即可，只需把 base_url 指向 DeepSeek：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from openai import OpenAI client = OpenAI( base_url=\u0026#34;https://api.deepseek.com\u0026#34;, api_key=\u0026#34;\u0026lt;your-deepseek-api-key\u0026gt;\u0026#34;, ) resp = client.chat.completions.create( model=\u0026#34;deepseek-v4-flash\u0026#34;, messages=[ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;你是一个严谨的编程助手。\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;用一句话说明什么是 API。\u0026#34;}, ], ) print(resp.choices[0].message.content) 提示：DeepSeek 同时提供 Anthropic 兼容端点（/anthropic）。如果你用的框架基于 Anthropic SDK，把 base_url 改成 https://api.deepseek.com/anthropic 即可复用同一套代码逻辑。\n","permalink":"https://sikinzen.github.io/posts/%E5%B8%B8%E8%A7%81%E5%BC%80%E5%8F%91%E8%BD%AF%E4%BB%B6%E7%9A%84%E9%83%A8%E5%88%86%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7/","summary":"Git 显示中文文件名、PowerShell 配色可读性、Cursor 接入 DeepSeek API，以及 repo init 的 GPG 签名校验问题，后续持续扩充。","title":"常见开发软件的部分使用技巧"},{"content":" 📚 本文是「Obsidian 知识管理」系列第三篇。同系列：\n从印象笔记到 Obsidian：一次完整的笔记迁移实战 Obsidian 双机同步：Git Sync + GitHub 私有仓库全攻略 一台常开台式机 + 一台笔记本 + 一部 Android 手机，怎么让三端的 Obsidian 笔记始终保持一致，又尽量不产生 Git 冲突？这套方案跑了大半个月，期间踩过一个典型的「半小时零同步」坑，本文把完整配置和复盘一并记录下来。\n一、为什么需要三设备同步 我的设备分布是这样的：\n设备 角色 使用习惯 Windows 台式机 主力编辑，24h 常开 大量写作、整理 Windows 笔记本 移动办公 随时读写 Android 手机 随身携带 以查阅为主，偶尔新增 / 修改 核心诉求有两条：\n三端一致：任何一端的修改，其他两端最终都能看到。 尽量少冲突：尤其是避免多方 Git 冲突导致整库无法同步。 第二条决定了架构分层的思路——让手机绕开 Git，只做文件级同步。\n二、整体架构 ┌──────────────┐ Vinzent Git (SSH) ┌────────────────┐ │ Windows 笔记本 │ ───────────────────▶ │ GitHub 仓库 │ └──────────────┘ ◀─────────────────── └────────────────┘ ▲ │ │ Vinzent Git (SSH) │ │ │ ┌─────┴──────┐ │ │ Windows 台式机 │ ◀───────────┘ │ (24h 常开) │ └─────┬──────┘ │ Syncthing (P2P 直连，不走 Git) ▼ ┌──────────────┐ │ Android 手机 │ └──────────────┘ 要点：\n双 Windows 机器走 Vinzent 的 Git 插件，通过 SSH 连接 GitHub 私有仓库，自动 commit / push / pull。 Android 手机不安装任何 Git 工具，改用 Syncthing 与台式机做 P2P 文件同步。 台式机是锚点：既跑 Git 插件，又跑 Syncthing，把手机的文件变化桥接进 Git 工作流。 为什么手机不碰 Git：手机（尤其境内无 Google Play 环境）装 Termux + Git + SSH 既麻烦又容易和 Windows 产生三方冲突；而 Syncthing 只做文件读写，从根上消除了手机端的 Git 冲突风险。\n三、Windows 双机：从 Git Sync 切换到 Vinzent 的 Git 插件 两台 Windows 机器的 Git 同步是整套方案的基础，但中间踩过插件选型的坑，单独说一下。\n3.1 为什么放弃 Git Sync 插件 最初用的是社区里的 Git Sync 插件（Livan Kumar，基于 GitHub OAuth，自动建仓库）。它上手快，但实战中经常产生冲突——本质原因是它通过 GitHub API 做提交 / 合并，在双机频繁交替编辑时，容易出现「远程已更新、本地落后」的合并失败，严重时整库卡住无法同步。\n3.2 改用 Vinzent 的 Git 插件 后来换成 Vinzent 的 Git 插件（在 Obsidian 社区插件商店里以 Git 为关键词搜索排名最高，常被称为 Obsidian Git）。它是本地调用 git 命令，行为更接近原生 git：\n提交 / 拉取 / 推送逻辑透明可控 自动同步间隔可配（commit、pull、push 分别设置） 冲突时就是标准 git 冲突，按文件解决，不会整库卡死 核心步骤：\n准备仓库：在 GitHub 创建私有仓库（如 my-vault），本地 git clone 到笔记目录（如 \u0026lt;你的本地笔记目录\u0026gt;）。 配置 SSH：生成 ed25519 密钥并添加到 GitHub → Settings → SSH and GPG keys；将 remote 设为 git@github.com:\u0026lt;你的GitHub用户名\u0026gt;/\u0026lt;仓库名\u0026gt;.git（SSH 比 HTTPS 更不容易被公司防火墙拦截）。 安装 Vinzent Git 插件：设置 → 第三方插件 → 浏览 → 搜索 Git → 安装启用（认准作者 Vinzent）。 开启自动同步：设置 Auto commit interval 与 Auto pull interval（建议 5–10 分钟）。 忽略工作区与同步配置：.gitignore 必须包含两项： .obsidian/ .stfolder/ .obsidian/ 是 Obsidian 的工作区配置（打开的标签页、面板布局等），不同设备不同，不应跨设备同步。 .stfolder/ 是 Syncthing 在每个同步文件夹里自动生成的标记目录，用于标识该目录由 Syncthing 管理。它会被 Git 误当成普通文件跟踪，导致跨设备 .stfolder 内容打架——所以必须忽略。 两台机器都指向同一个仓库、且都忽略上述两项后，双机同步即建立。后续新增的 Android 只是挂在台式机这一个锚点上。\n双机 Git 同步的早期尝试（Git Sync 插件 + OAuth）可参考本博客的《Obsidian 双机同步》一文，其中记录了该插件的配置与踩坑——也正是因为它易冲突，才催生了本文的 Vinzent Git 方案。\n四、Android 端准备 4.1 安装 Obsidian 优先在 Google Play 搜索 Obsidian 安装。 无 Google Play 的设备，可从 Obsidian 官网 下载 APK 侧载（开启「未知来源」安装权限）。 安装后先不急着创建 vault，等 Syncthing 把笔记同步到手机目录后，再用 Obsidian 打开那个目录即可（见第六节）。 4.2 安装 Syncthing（实测于境内网络） Syncthing 官方 Android 版已于 2024-12 停止维护，最后版本为 1.28.1（包名 com.nutomic.syncthingandroid）。APK 获取渠道与实测结果：\n渠道 地址 实测 GitHub Releases 直链 https://github.com/syncthing/syncthing-android/releases/download/1.28.1/app-release.apk ✅ 可正常下载 F-Droid 页面 https://f-droid.org/en/packages/com.nutomic.syncthingandroid/ ❌ 实测无法下载 F-Droid 直链 https://f-droid.org/repo/com.nutomic.syncthingandroid_4380.apk ❌ 实测无法下载 下载后侧载安装，首次打开授予文件访问权限。\n实测结论：在境内网络环境下，GitHub Releases 直链可正常下载；F-Droid 的页面与直链均不可用。直接走 GitHub 直链即可，不必折腾 F-Droid。\n五、Syncthing 配对与文件夹共享（核心） 这是整篇文章最关键的部分。Syncthing 的同步不是「加个设备就完事」，而是设备配对 + 文件夹共享邀请 + 对端接受三步，漏掉最后一步就会像我一样卡在「已连接但零字节」。\n5.1 台式机：安装并获取设备 ID 台式机从 syncthing.net 下载 Windows 版，运行 syncthing.exe。 浏览器打开管理界面 http://127.0.0.1:8384。 右上角「操作」→「显示 ID」，复制这台机器的设备 ID（一长串字符）。 5.2 手机：添加远程设备 打开手机 Syncthing → 底部「设备」→「+」→「添加设备」。 粘贴台式机的设备 ID（或扫描台式机界面上的二维码）。 设备名随意（如 我的台式机），保存。 台式机端会弹出「远程设备想要连接」提示，点「添加」确认。 此时两端已互相识别、状态显示「已连接」。但到此为止还不会同步任何文件——因为还没有共享文件夹。\n5.3 台式机：把文件夹共享给手机 台式机 Syncthing Web UI →「文件夹」→ 选中你的笔记文件夹（如 my-vault）→「编辑」→「共享」。 勾选刚才添加的手机设备。 保存。 保存后，台式机会向手机发送一份「文件夹共享邀请」。\n5.4 手机：接受共享邀请（关键步骤） 这一步是大多数人卡住的地方：\n打开手机 Syncthing → 右下角「设置」（齿轮）→ Web GUI。 手机本地的 Syncthing Web GUI 会在浏览器中打开（通常是 http://127.0.0.1:8384）。 进入后，界面顶部会出现一条通知：「远程设备 XXX 想要共享文件夹 YYY」。 点击该通知 → 「接受」。 在弹出的对话框中选择手机上存放该 vault 的本地路径（如 \u0026lt;你的手机笔记目录\u0026gt;），确认。 部分版本的 Syncthing App 也会在「文件夹」页直接弹出共享邀请卡片，操作同理：点接受 → 选路径。\n接受之后，双向同步才真正开始。 台式机有完整笔记、手机是空目录，Syncthing 默认 发送和接收 模式，会自动把台式机的内容拉取到手机。\n5.5 忽略 .obsidian/ 与 .stfolder/ 在手机端该文件夹的「忽略模式」里添加：\n.obsidian/ .stfolder/ .obsidian/：不同设备的 Obsidian 工作区配置不同，不应跨设备同步，否则界面布局、打开的标签页会互相覆盖。 .stfolder/：Syncthing 自身的标记目录，不应被同步（同步了反而会造成两端 Syncthing 互相干扰）。 Windows 端若已在 .gitignore 忽略这两项，此处再在 Syncthing 忽略一次，双保险。\n六、Android 端 Obsidian 打开 vault 等首次同步完成（手机目录出现所有 .md 文件），再让 Obsidian 接管：\n打开手机 Obsidian → 「打开其他仓库」→「打开文件夹作为仓库」。 选择 Syncthing 同步过来的目录（如 \u0026lt;你的手机笔记目录\u0026gt;）。 笔记即刻全部显示，且后续手机上的修改会经 Syncthing 实时写回该目录。 顺序上先同步、后打开 Obsidian 最稳，能避免 Obsidian 在空目录里生成一套自己的 .obsidian 后再被 Syncthing 覆盖的尴尬。\n七、踩坑实录：半小时零同步的真相 这部分值得单独写出来，因为它代表了一类非常典型的误判。\n现象 配对完成后，台式机 Web UI 显示手机设备「已连接」，但：\n同步进度长期 0%，待同步 92.2 MiB 连接类型显示「中继广域网」，速率 1 B/s 半小时后手机目录一个文件都没有 第一反应（错误） 看到「中继」+「1 B/s」，很自然地联想到：Syncthing 公共中继服务器在境外，境内访问被限速，所以中继不可用。于是去查 VPN、准备换 FolderSync + 坚果云等替代方案。\n真相（正确） 根因根本不是网络，而是文件夹共享邀请没被接受。\n只在一端「添加设备」并「共享文件夹」还不够——对端必须在 Syncthing 里明确「接受」这份共享邀请，数据通道才会建立。在此之前，两端虽然「已连接」，但没有任何文件夹处于共享状态，自然零字节。所谓 1 B/s 只是连接保活的探测流量，不是真实传输。\n一旦按第五节在手机端 设置 → Web GUI 里接受邀请，连接类型依旧可能显示「中继」（取决于当时网络是否能直连），但速率立刻恢复正常、进度开始增长，几分钟内手机拿到全部笔记——全程没有开任何 VPN。\n经验 Syncthing「已连接 ≠ 已同步」。看到「已连接」先检查两端是否都接受了彼此的文件夹共享，而不是急着怀疑网络。 境内使用 Syncthing 不一定要翻墙。能否直连取决于网络环境（IPv6、NAT 类型等），即便走中继，只要共享流程完整，速度通常也可接受。 排查顺序建议：设备在线 → 文件夹已互相共享且对端已接受 → 忽略模式无误 → 再看连接类型与速率。 八、验证与冲突规避 检查项 预期 台式机修改笔记 数分钟后手机 Obsidian 中出现 手机修改 / 新增笔记 经 Syncthing 写回目录 → 台式机 Git 插件自动提交 → 推送 GitHub → 笔记本拉取 连接状态 台式机 Web UI 中手机设备「已连接」、文件夹进度 100% 冲突规避要点：\n手机不执行 Git 操作，从源头消除手机端 Git 冲突。 .obsidian/ 与 .stfolder/ 在 Git 与 Syncthing 双重忽略。 尽量避免手机与 Windows 同时编辑同一篇笔记；万一冲突，Syncthing 会生成 .sync-conflict- 副本，不丢数据，手动合并即可。 台式机保持常开，确保 Git 插件自动提交 / 拉取持续运行，桥接手机与 GitHub。 九、维护清单 台式机 24h 常开，Syncthing 与 Obsidian 后台运行 台式机 Git 插件：Auto commit / Auto pull 间隔 5–10 分钟 每季度确认一次手机 ↔ 台式机 Syncthing 连接正常 关注 Syncthing Android 停更后的替代方案（FolderSync / 社区 Fork），但当前 1.28.1 仍可正常使用 含账户密码的笔记（如备忘录）注意不要误推到公开仓库 十、小结 三设备同步的本质是职责分层：\nGit（Vinzent 插件） 负责双 Windows 机器的版本化同步，稳、可追溯； Syncthing 负责手机与锚点台式机的文件级同步，简单、无冲突； 台式机作为桥接点，把两者的变化汇流。 最容易翻车的不是网络，而是 Syncthing 的「共享邀请需对端接受」这一步——记住它，能省下我那白绕一圈的半小时。\n最后更新：2026-07-12\n","permalink":"https://sikinzen.github.io/posts/obsidianthreedevicesync/","summary":"Obsidian 三设备（Windows 双机 + Android）同步实战：Git 管双机（Vinzent Git 插件），Syncthing 管手机，含共享接受关键步骤与踩坑复盘。","title":"Obsidian 三设备同步实战：Windows 双机 Git + Android Syncthing"},{"content":"背景 团队 SVN 服务器使用 VisualSVN Server，仓库大小已超过 100GB。最近想做定期自动备份，结果发现：\n「备份任务（Backup Jobs）是 Enterprise Edition 功能，您当前使用的是 Standard Edition。」\n没有预算升级，只能自己动手。本文记录了从 .bat 批处理到纯 PowerShell、经历 6 轮迭代才跑通的全过程，并提供可直接下载使用的完整方案。\n一、快速开始：下载附件，按部署指南操作 如果你只是想要一个能用的备份方案，不想看踩坑过程，直接按以下步骤来。\n1.1 下载三个文件 文件 用途 下载 svn_backup.ps1 主备份脚本——执行 hotcopy、磁盘预检、日志记录、旧备份清理 ⬇ 下载 setup_svn_backup_task.ps1 计划任务配置脚本——一键注册 Windows 定时任务 ⬇ 下载 SVN-Backup-Usage-Guide.md 完整部署与使用指南——从零到跑通的每一步说明 ⬇ 下载 1.2 部署（照着 Guide 做） 将两个 .ps1 放到 SVN 服务器上（建议 D:\\Scripts\\） 打开 svn_backup.ps1，找到文件头部 CONFIG 标记处，修改仓库路径和仓库名列表 打开 SVN-Backup-Usage-Guide.md，按第五章「部署步骤」操作：先手动测试一次备份，再注册计划任务 计划任务默认每周日凌晨 02:00 运行；注册成功后可在任务计划程序（taskschd.msc）中搜索 SVN Hotcopy Backup 找到它 部署细节全部写在 Guide.md 里了——包括如何配置、如何排查执行策略错误、如何验证任务是否创建成功等。下面第二节和第三节分别讲恢复流程和方案原理，部署就不重复展开了。\n二、实战恢复指南 备份的意义在于能恢复。这一节是很多人忽视但最关键的部分——svnadmin hotcopy 的产出物是可直接使用的完整仓库，目录结构、revision 历史、hooks、锁信息全部保留，无需 svnadmin load 中间环节。\n下文以备份路径 E:\\SVN_Backup\\仓库A_20260628_020015、原始仓库 D:\\Repositories\\仓库A 为例。\n2.1 恢复前：验证备份可用性（30 秒搞定） 1 2 3 4 5 6 7 8 9 10 # 确认备份目录结构完整（format 文件是 SVN 仓库的特征） Test-Path E:\\SVN_Backup\\仓库A_20260628_020015\\format # 返回 True → 通过 # 读取最新 revision 号，与备份日志中记录的对比 svnlook youngest E:\\SVN_Backup\\仓库A_20260628_020015 # 检查备份日志是否有报错 Get-Content E:\\SVN_Backup\\logs\\backup_*.log | Select-String \u0026#34;ERROR\u0026#34; # 空输出 = 无错误 2.2 场景一：原仓库损坏，直接替换恢复（最常见） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 1. 停服 Stop-Service VisualSVNServer -Force # 2. 重命名损坏仓库（留后路，别直接删） Rename-Item D:\\Repositories\\仓库A D:\\Repositories\\仓库A_notused # 3. 复制备份到正式仓库位置（复制而非移动，保留备份副本） Copy-Item -Recurse E:\\SVN_Backup\\仓库A_20260628_020015 D:\\Repositories\\仓库A # 4. 启动服务 Start-Service VisualSVNServer # 5. 验证 svn info http://your-server/svn/仓库A --username your_account 停服时间 = 复制耗时。120GB 仓库本地复制约 10-30 分钟，取决于磁盘性能。\n2.3 场景二：临时恢复独立副本（用于查历史/提取文件，无需停服） 1 2 3 4 5 6 7 8 9 10 11 12 13 # 复制备份到临时位置，完全不影响生产仓库 Copy-Item -Recurse E:\\SVN_Backup\\仓库A_20260628_020015 D:\\Temp\\仓库A_restore # 用 file:// 协议直接访问 svn list file:///D:/Temp/仓库A_restore/trunk/ svn cat file:///D:/Temp/仓库A_restore/trunk/某文件.txt -r 10000 # 用 TortoiseSVN 浏览更直观： # 右键 → TortoiseSVN → Repo-browser → URL 填 file:///D:/Temp/仓库A_restore/ # 如需多人临时访问，快速拉起 svnserve： svnserve -d -r D:\\Temp\\仓库A_restore --listen-port 3691 # 其他人通过 svn://your-ip:3691/ 即可只读访问 2.4 场景三：精准恢复特定文件或目录 只想找回某个被误删的文件，不需要整体回滚：\n1 2 3 4 5 6 7 8 # 导出指定路径所有文件 svn export file:///D:/Temp/仓库A_restore/trunk/src/某模块/ D:\\RecoveredFiles\\ # 导出指定路径在某个历史版本下的状态 svn export -r 11500 file:///D:/Temp/仓库A_restore/trunk/src/某模块/ D:\\RecoveredFiles_v11500\\ # 查看某个文件在多个版本间的变更 svn log -r 10000:11826 file:///D:/Temp/仓库A_restore/trunk/src/某文件.cs 2.5 恢复后验证清单 检查项 命令 预期 完整性 svnlook youngest D:\\Repositories\\仓库A 与备份日志中的 revision 一致 服务状态 Get-Service VisualSVNServer Running 可达性 svn info http://server/svn/仓库A 正常返回 权限 用普通账号 checkout 无 403/401 提交 测试 commit 后立刻还原 正常 1 2 3 4 5 6 # 一键验证 $REPO = \u0026#34;D:\\Repositories\\仓库A\u0026#34; Write-Host \u0026#34;Revision: $(svnlook youngest $REPO)\u0026#34; Write-Host \u0026#34;UUID : $(svnlook uuid $REPO)\u0026#34; Write-Host \u0026#34;Service : $((Get-Service VisualSVNServer).Status)\u0026#34; svn info http://your-server/svn/仓库A 2\u0026gt;\u0026amp;1 | Select-String \u0026#34;Revision:\u0026#34; 2.6 关键注意事项 备份盘与生产仓库必须物理隔离。 生产仓库在 D 盘，备份在 E 盘，这是最低要求。如果两盘是同一块物理磁盘的不同分区，应额外拷贝一份到 NAS 或独立硬盘。\n首次恢复先在场景二（独立副本）上完整走一遍，确认耗时和步骤心中有数。\n保留至少 2-3 份不同日期的备份（本方案默认 30 天），最新备份万一有小概率损坏还能回退。\n停服前通知团队。 服务中断期间所有 svn update/commit 都会失败。\n三、方案选型与原理 3.1 为什么不用增量备份？ 备份方式 类型 恢复步骤 适合场景 svnadmin hotcopy 全量 备份目录直接可用，一步到位 仓库 \u0026lt; 200GB，重视恢复速度 svnadmin dump --incremental 增量 需先恢复全量基础 + 逐个加载增量 超大仓库 / 异地传输带宽有限 svnsync 增量镜像 镜像仓库直接可用 异地容灾，网络可达 我们的仓库约 120GB，全量 hotcopy 耗时约 1 小时，备份盘空间充足。恢复时一步到位是我们选择 hotcopy 的核心原因——灾难时刻最怕操作复杂。\n3.2 最终架构 svnadmin hotcopy 全量备份 ↓ PowerShell 脚本（磁盘预检 + 日志 + 旧备份清理） ↓ Windows 任务计划程序（每周日凌晨 02:00 自动执行，SYSTEM 最高权限） 3.3 脚本核心流程 Step 1: 清理超过保留天数的旧备份目录 Step 2: 遍历仓库列表，逐个执行： ├── 计算仓库大小 ├── 检查备份盘剩余空间 ≥ 仓库 × 2 ├── svnadmin hotcopy 全量复制 ├── 验证备份完整性（检查 format 文件 + 读取 revision） └── 失败时自动清理不完整目录 Step 3: 输出备份汇总（成功/失败的仓库、总耗时、总大小） 四、踩坑实录：从 .bat 到 PowerShell 的 6 轮迭代 4.1 为什么不用 .bat——四个致命坑 最初觉得需求简单，用 Windows 批处理就够了。结果连踩四个坑，每个单独看都能解决，但叠加在一起让 .bat 方案彻底失败。\n坑 1：UTF-8 编码 → 中文注释被解析为命令\n脚本保存为 UTF-8 格式，运行时 cmd.exe 按 ANSI/GBK 解析，所有中文注释和变量名被切成碎片：\n\u0026#39;已启用延迟扩展\u0026#39; 不是内部或外部命令 \u0026#39;热备份\u0026#39; 不是内部或外部命令 修复方式：所有注释和日志改为纯英文。但这只是开始。\n坑 2：32 位整数溢出\n100GB+ 仓库的原始字节数 ≈ 1073 亿，远超批处理 set /a 的 32 位上限（约 21 亿）。磁盘空间计算直接溢出。\n修复方式：改用内嵌 PowerShell 以 GB 为单位计算。但引入了第三个问题。\n坑 3：管道转义 + for /f 嵌套崩溃\n在 for /f 中嵌套 powershell ... | ...，多层转义下 cmd.exe 解析彻底错乱：\n\u0026#39;usebackq\u0026#39; 不是内部或外部命令 命令语法不正确 试了 3 轮都没修好——for /f 内部的 |、^ 转义规则极其脆弱。\n坑 4：LF 行尾 vs CRLF\n修改文件时编辑器保存为 LF（Unix）行尾，导致 cmd.exe 把整个文件当成一行解析：\n\u0026#39;setlocal enabledelayedexpansion\u0026#39; 不是内部或外部命令 用 sed 转回 CRLF 后，最终还是被坑 3 击倒。\n结论：放弃 .bat。\n4.2 PowerShell 方案的两个隐藏坑 用 PowerShell 重写后，前四个问题一劳永逸——原生 UTF-8、64 位运算、健壮管道、无行尾困扰。但新坑立刻出现：\n坑 5：WMI DeviceID 路径匹配（最难发现）\n1 2 3 4 5 # 这个返回 \u0026#34;E:\\\u0026#34;（带反斜杠） [System.IO.Path]::GetPathRoot(\u0026#34;E:\\VisualSVN Server\\Backup\u0026#34;) # 但 WMI 中 DeviceID 是 \u0026#34;E:\u0026#34;（不带反斜杠） Get-CimInstance Win32_LogicalDisk | Select DeviceID E:\\ ≠ E:，WMI 查询永远匹配不到，磁盘空间始终返回 0 GB——脚本因此拒绝备份。这是最难排查的 bug，因为命令行直接测试 WMI 时通常不会凭空多出一个反斜杠。\n修复只需一行：\n1 $Drive = $Drive.TrimEnd(\u0026#39;\\\u0026#39;) # \u0026#34;E:\\\u0026#34; → \u0026#34;E:\u0026#34; 坑 6：PowerShell 执行策略\n计划任务配置脚本无法直接运行：\n无法加载文件 setup_svn_backup_task.ps1，因为在此系统上禁止运行脚本 Windows 默认 Restricted 策略。修复同样简单——用 -ExecutionPolicy Bypass 参数绕过：\n1 PowerShell -NoProfile -ExecutionPolicy Bypass -File D:\\Scripts\\setup_svn_backup_task.ps1 4.3 六个坑汇总 序号 陷阱 症状 根因 解决方案 1 UTF-8 编码 中文注释被解析为命令 cmd.exe 按 ANSI 解析 英文注释，或换 PowerShell 2 32 位整数溢出 磁盘空间计算返回乱码 set /a 上限 21 亿 换 PowerShell 64 位运算 3 管道转义 for /f + | 嵌套崩溃 cmd.exe 转义规则脆弱 放弃 .bat 4 LF 行尾 整个文件当一行解析 批处理必须 CRLF 用 sed 转行尾，或换 PowerShell 5 WMI 路径匹配 磁盘空间返回 0 GB E:\\ ≠ E: $Drive.TrimEnd('\\') 6 执行策略 .ps1 禁止运行 系统默认 Restricted -ExecutionPolicy Bypass 核心教训：不要用 .bat 写超过 20 行的自动化脚本。 批处理的设计年代没有 Unicode、没有 64 位、没有健壮的管道——用它在 2026 年处理 100GB 级任务，是在对抗历史包袱。\n五、附件下载与修改说明 下载后务必先修改配置区再部署。每个文件头部都有 CONFIG 标记，方便定位。\n5.1 文件清单 文件 用途 下载 svn_backup.ps1 主备份脚本 ⬇ 下载 setup_svn_backup_task.ps1 计划任务配置脚本 ⬇ 下载 SVN-Backup-Usage-Guide.md 完整部署与使用指南 ⬇ 下载 5.2 svn_backup.ps1 配置项 打开文件，定位到 CONFIG 标记处：\n1 2 3 4 5 $REPO_BASE = \u0026#34;D:\\Repositories\u0026#34; # [必改] 仓库根目录 $BACKUP_ROOT = \u0026#34;E:\\SVN_Backup\u0026#34; # [必改] 备份存放目录（必须与生产仓库不同物理磁盘） $RETENTION_DAYS = 30 # [选改] 旧备份保留天数 $SPACE_SAFETY_FACTOR = 2 # [选改] 空间安全系数 $REPOS = @(\u0026#34;repo1\u0026#34;, \u0026#34;repo2\u0026#34;, \u0026#34;repo3\u0026#34;) # [必改] 仓库名称列表（对应 REPO_BASE 下的子目录名） $REPOS 中的值必须是 $REPO_BASE 下的子目录名。例如仓库路径是 D:\\Repositories\\MyProject，则填 \u0026quot;MyProject\u0026quot;，不是完整路径。\n5.3 setup_svn_backup_task.ps1 配置项 1 2 3 $ScriptPath = \u0026#34;D:\\Scripts\\svn_backup.ps1\u0026#34; # [必改] svn_backup.ps1 的实际路径 $ScheduleTime = \u0026#34;02:00\u0026#34; # [选改] 执行时间（默认周日凌晨 2 点） $TaskName = \u0026#34;SVN Hotcopy Backup\u0026#34; # [选改] 任务计划程序中的显示名称 5.4 部署验证四步走 1 2 3 4 5 6 7 8 9 10 11 # 1. 手动执行一次备份，确认无报错 PowerShell -NoProfile -ExecutionPolicy Bypass -File D:\\Scripts\\svn_backup.ps1 # 2. 第 1 步成功后，注册计划任务（以管理员身份） PowerShell -NoProfile -ExecutionPolicy Bypass -File D:\\Scripts\\setup_svn_backup_task.ps1 # 3. 确认任务已注册 Get-ScheduledTask -TaskName \u0026#34;SVN Hotcopy Backup\u0026#34; # 4. 检查日志 Get-Content E:\\SVN_Backup\\logs\\backup_*.log -Tail 30 详细的配置说明、手动测试步骤、任务计划程序内查找任务的方法、以及 PowerShell 执行策略的三种绕过方式，均已在 SVN-Backup-Usage-Guide.md 中逐项说明。本文不再重复，请参考该文档。\n如果你也在维护 VisualSVN Standard Edition 的 SVN 服务器，希望能帮你省下这 6 轮踩坑的时间。\n","permalink":"https://sikinzen.github.io/posts/svnhotcopybackupguide/","summary":"100GB+ SVN 仓库自动备份方案的完整实战：.bat 的 5 个致命陷阱 → 纯 PowerShell 重写 → WMI 路径匹配修复 → 执行策略绕过，附完整脚本和填坑教训","title":"SVN 大仓库自动备份实战：用 svnadmin hotcopy 替代企业版，填坑全过程记录"},{"content":" 前文 WorkBuddy 自动化任务打通 QQ 消息推送 详细记录了从企微失败到 QQ Bot 群推送打通的完整踩坑过程。本文在此基础上，讲解如何把这个折腾了两小时的方案封装成一个标准 WorkBuddy Skill，让后续任何人（或 AI）加载后即插即用。\n为什么需要封装成 Skill 打通 QQ 推送后我们手里有的东西：\n资产 形态 问题 qq_get_group_openid.py Python 脚本 散落文件，凭据写死在代码里 qq_send_group_msg.py Python 脚本 同上，用法靠人记 踩坑经验 大脑记忆 下次重装系统全忘光 集成到自动化任务 Prompt 中写死路径 换个环境就要改 这些资产的共同问题：不可复制、不可分发、不可维护。\nSkill 解决的就是这个——把代码、文档、踩坑经验打成一个包，放在 ~/.workbuddy/skills/ 目录下，WorkBuddy 的 AI 在合适的时机自动加载它。对人和 AI 都友好。\nSkill 的文件结构 一个标准 Skill 的目录结构如下：\n~/.workbuddy/skills/\u0026lt;skill-name\u0026gt;/ ├── SKILL.md # 核心文件：AI 的行为指南 + 使用文档 ├── scripts/ # 可执行脚本（Python、Shell 等） │ ├── qq_get_group_openid.py │ └── qq_send_group_msg.py ├── references/ # 参考资料（故障排查表、API 文档等） │ └── troubleshooting.md └── assets/ # 静态资源（模板文件、配置示例等） └── (按需使用) 最关键的只有一个文件：SKILL.md。它是 AI 加载 Skill 后读取的第一份文档，决定 AI 如何理解和使用这个 Skill。其余目录按需存在。\n实战：将 qq-group-push 封装为 Skill 以下按实际操作顺序记录，可以直接复现。\nStep 1：初始化 Skill 目录 WorkBuddy 内置的 skill-creator 提供了 init_skill.py 脚本，自动生成标准骨架：\n1 2 3 4 # 使用 WorkBuddy 的 managed Python（路径可能不同，用你自己的 Python） python \u0026#34;C:/Users/\u0026lt;user\u0026gt;/AppData/Local/Programs/WorkBuddy/resources/app.asar.unpacked/resources/builtin-skills/skill-creator/scripts/init_skill.py\u0026#34; \\ qq-group-push \\ --path \u0026#34;C:/Users/\u0026lt;user\u0026gt;/.workbuddy/skills/\u0026#34; 执行后生成：\n.workbuddy/skills/qq-group-push/ ├── SKILL.md # 模板文件，待填充 ├── scripts/ │ └── example.py # 示例脚本（可删除） ├── references/ │ └── api_reference.md # 示例参考（可删除） └── assets/ └── example_asset.txt # 示例资源（可删除） 把自动生成的示例文件删掉：\n1 rm scripts/example.py references/api_reference.md assets/example_asset.txt Step 2：将脚本复制进 scripts/ 直接把两个已脱敏的 Python 脚本放进去（qq_get_group_openid.py / qq_send_group_msg.py）：\n1 2 cp qq_get_group_openid.py ~/.workbuddy/skills/qq-group-push/scripts/ cp qq_send_group_msg.py ~/.workbuddy/skills/qq-group-push/scripts/ 脱敏要求：复制前确认脚本中的 APPID、SECRET 等已替换为占位符（如 YOUR_APPID），不要带真实凭据。\nStep 3：编写故障排查参考 references/troubleshooting.md 这部分是整个封装过程中投入产出比最高的环节。之前踩过的 7 个坑（错误码 100016/340067、Intents 参数变化、botpy 导入路径、GBK 编码、沙箱文件写入、botpy.log 权限等），全部整理成一张按错误症状索引的排查表。\n核心格式：错误信息/错误码 → 根因分析 → 修复步骤。例如：\n1 2 3 4 5 6 7 8 9 ### 100016 — invalid appid or secret **Cause:** AppID or AppSecret rejected by QQ server. **Fix checklist:** 1. Go to QQ Open Platform → select bot → 开发设置 2. Verify AppID (pure digits, no spaces) 3. Click \u0026#34;Reset Secret\u0026#34; to get a fresh AppSecret (shown only once) 4. Ensure bot status is **已上线**, not **开发中** 完整排查表见附件 qq-group-push-troubleshooting.md。\nStep 4：编写核心文件 SKILL.md 这是最需要花心思的部分。SKILL.md 由两部分组成：YAML Front Matter（给 WorkBuddy 系统读）和Markdown 正文（给 AI 和人读）。\n4.1 Front Matter 字段说明 1 2 3 4 5 6 7 8 9 --- name: qq-group-push # Skill 标识名（kebab-case） description: \u0026gt; # 触发条件 + 功能概述 This skill enables WorkBuddy automation tasks to push messages to QQ groups via the official QQ Bot API. Use when the user wants to send QQ group messages from WorkBuddy automations. Triggers: QQ group push, QQ bot message, QQ 群推送, QQ 机器人发消息. agent_created: true # 标记为 AI 创建，允许 SkillManage 修改 --- 字段 必填 说明 name 是 Skill 标识名，用 kebab-case（小写 + 连字符）。用户不说也可能触发，所以取名要表达用途 description 是 最重要。WorkBuddy 根据此字段判断何时激活 Skill。必须包含：(1) 功能描述 (2) 触发关键词（中英文都要） agent_created 否 设为 true 后允许通过 SkillManage 更新/修改这个 Skill。AI 创建的 Skill 必须加此标记 description 编写技巧：想象一个从未见过这个 Skill 的 AI，它只会看这段描述来决定\u0026quot;我要不要加载这个 Skill\u0026quot;。所以关键词越全越好，中英文都要覆盖。\n4.2 正文结构 正文推荐以下五段式结构（这是反复踩坑后沉淀的最佳实践）：\n1 2 3 4 5 6 7 8 # \u0026lt;Skill名称\u0026gt; — \u0026lt;一句话定位\u0026gt; ## Overview ← 一句话讲清楚这个 Skill 是干嘛的 ## Prerequisites (one-time setup) ← 一次性环境准备，明确列出所有前置依赖 ## Step-by-Step Workflow ← 核心：按操作顺序分步骤，每步包含命令和预期结果 ## Important Technical Notes ← 关键踩坑点：强制 WebSocket、GBK emoji、沙箱问题 ## Scripts ← 每个脚本的入参、出参、依赖文件说明 ## Integration Patterns ← 三种典型的自动化任务集成方式 参考实际 SKILL.md 的完整内容（已作为附件），特别注意以下几点：\nPrerequisites 要穷尽：如果用户没装 qq-botpy，Skill 加载后 AI 会尝试 pip install，但前提是你在 SKILL.md 里写明了这个依赖 Workflow 用祈使句：每条操作指令直接可执行，不要加\u0026quot;你可以\u0026quot;、\u0026ldquo;建议你\u0026quot;这类前缀 Technical Notes 即踩坑清单：所有踩过的坑都写成\u0026quot;约束 + 解决方案\u0026quot;格式，AI 在遇到问题时会自动查这部分 Integration Patterns 覆盖典型场景：直接调脚本、文件模式、失败回退——三种就够了 4.3 一段好的 SKILL.md 正文长什么样 以下截取自 qq-group-push 的 Step-by-Step Workflow 部分，展示\u0026quot;操作指令化\u0026quot;的正确写法：\n1 2 3 4 5 6 7 8 ### Step 1: Capture group_openid 1. Edit `scripts/qq_get_group_openid.py`: replace `YOUR_APPID` and `YOUR_APPSECRET` with real credentials. 2. Run the script (background it - it listens for WebSocket events): ```bash python scripts/qq_get_group_openid.py In the QQ group, send an @bot message (any content, e.g. \u0026ldquo;获取openid\u0026rdquo;). The script prints GROUP_OPENID=\u0026lt;value\u0026gt; to stdout, saves it to qq_group_openid.txt, and exits. 注意特征： - **编号步骤** = 操作顺序不可乱的信号 - **代码块紧跟说明** = AI 可以直接 `bash` 执行 - **输入/输出明确声明** = 方便 AI 判断是否成功 ### Step 5：清理与验证 ```bash # 删除空目录 rmdir assets/ # 确认最终结构 find ~/.workbuddy/skills/qq-group-push -type f 预期输出：\n.workbuddy/skills/qq-group-push/SKILL.md .workbuddy/skills/qq-group-push/scripts/qq_get_group_openid.py .workbuddy/skills/qq-group-push/scripts/qq_send_group_msg.py .workbuddy/skills/qq-group-push/references/troubleshooting.md 此时 Skill 已完成封装。WorkBuddy 下次识别到\u0026quot;QQ 推送\u0026rdquo;、\u0026ldquo;QQ 群发消息\u0026quot;等关键词时，会自动加载此 Skill。\n将 Skill 集成到自动化任务 封装完成后，自动化任务的 prompt 中只需一行引用：\n调用 QQ 群推送脚本发送消息： python D:\\Work\\AI\\AiMemory\\Claw\\qq_send_group_msg.py \u0026#34;消息内容\u0026#34; 因为 Skill 已经提供了完整的 troubleshooting.md，AI 在执行失败时会自动查阅错误码、对比根因、尝试修复（例如检测到 100016 错误时会提示检查 AppSecret 是否正确）。\n而用户侧要做的，只是在第一次使用时：\n安装 qq-botpy 注册 QQ Bot 获取凭据 创建一个 QQ 群 运行 qq_get_group_openid.py 获取群标识 即可在 10 分钟内完成从零到推送。\n封装过程中踩到的坑 踩坑 根因 解决 init_skill.py 创建后示例文件未清理 模板包含 example.py 等无意义的占位文件 立即 rm 掉，只保留三个核心文件 SKILL.md 第一版 description 写得太简略 WorkBuddy 靠 description 决定是否加载 Skill，关键词不全会导致\u0026quot;触发不了\u0026rdquo; 中英文关键词全覆盖，用\u0026quot;Triggers:\u0026quot; 后缀明确列出 references/ 写成流水账而非排查表 AI 读参考资料时是按错误码索引的，不是按叙事顺序 改为\u0026quot;症状→根因→修复\u0026quot;的三段式表格结构 忘记在 SKILL.md 里写 Prerequisites AI 加载 Skill 后不知道要 pip install qq-botpy，执行脚本直接报 ImportError 把 pip 安装命令写在 Prerequisites 的第一条 Front Matter 漏了 agent_created: true SkillManage 无法修改非 agent_created 的 Skill，后续改进会被拦截 init 后立即补上该字段 Skill 封装的标准流程（总结） 把以上实战抽象为一套可复用的 SOP：\n1. init_skill.py → 生成骨架目录 2. 清理示例文件 → 删 example.py / api_reference.md / example_asset.txt 3. 搬入脚本 → cp 到 scripts/ 4. 编写 troubleshooting → 按\u0026#34;症状→根因→修复\u0026#34;格式列出所有踩坑 5. 编写 SKILL.md → 按五段式填充：Overview / Prerequisites / Workflow / Notes / Patterns 6. 补 agent_created → Front Matter 加 agent_created: true 7. 验证触发 → 在 WorkBuddy 中用关键词测试是否自动加载 Skill 花时间最多的是 Step 5 的 description 字段和 Step 4 的 troubleshooting——这两个才是 Skill 的核心价值，代码只是载体。\n附件下载 以下文件可直接下载使用（已脱敏，替换占位符即可）：\n文件 下载 说明 SKILL.md 下载 QQ 群推送 Skill 的完整主文档（可直接放入 ~/.workbuddy/skills/） qq_get_group_openid.py 下载 获取 QQ 群 group_openid（WebSocket 事件监听） qq_send_group_msg.py 下载 发送 QQ 群消息（WebSocket 连接 + 发送 + 退出） troubleshooting.md 下载 7 个常见错误的排查速查表 附件中所有 YOUR_APPID、YOUR_APPSECRET、YOUR_GROUP_OPENID 均为占位符，使用前需替换为真实值。\n相关资源 WorkBuddy 自动化任务打通 QQ 消息推送：完整踩坑实录 — 前篇：打通 QQ 推送的技术细节 QQ 开放平台 Bot API 文档 — 官方 API 接口 botpy Python SDK — 腾讯官方 Python SDK WorkBuddy Skill Creator — Skill 创建官方指南 ","permalink":"https://sikinzen.github.io/posts/workbuddyskillencapsulation/","summary":"\u003cblockquote\u003e\n\u003cp\u003e前文 \u003ca href=\"https://sikinzen.github.io/posts/workbuddyqqbotpush/\"\u003eWorkBuddy 自动化任务打通 QQ 消息推送\u003c/a\u003e 详细记录了从企微失败到 QQ Bot 群推送打通的完整踩坑过程。本文在此基础上，讲解如何把这个折腾了两小时的方案\u003cstrong\u003e封装成一个标准 WorkBuddy Skill\u003c/strong\u003e，让后续任何人（或 AI）加载后即插即用。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"为什么需要封装成-skill\"\u003e为什么需要封装成 Skill\u003c/h2\u003e\n\u003cp\u003e打通 QQ 推送后我们手里有的东西：\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e资产\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e形态\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e问题\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eqq_get_group_openid.py\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePython 脚本\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e散落文件，凭据写死在代码里\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eqq_send_group_msg.py\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePython 脚本\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e同上，用法靠人记\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e踩坑经验\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e大脑记忆\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e下次重装系统全忘光\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e集成到自动化任务\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePrompt 中写死路径\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e换个环境就要改\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e这些资产的共同问题：\u003cstrong\u003e不可复制、不可分发、不可维护\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003eSkill 解决的就是这个——把代码、文档、踩坑经验打成一个包，放在 \u003ccode\u003e~/.workbuddy/skills/\u003c/code\u003e 目录下，WorkBuddy 的 AI 在合适的时机自动加载它。对人和 AI 都友好。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"skill-的文件结构\"\u003eSkill 的文件结构\u003c/h2\u003e\n\u003cp\u003e一个标准 Skill 的目录结构如下：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e~/.workbuddy/skills/\u0026lt;skill-name\u0026gt;/\n├── SKILL.md              # 核心文件：AI 的行为指南 + 使用文档\n├── scripts/              # 可执行脚本（Python、Shell 等）\n│   ├── qq_get_group_openid.py\n│   └── qq_send_group_msg.py\n├── references/           # 参考资料（故障排查表、API 文档等）\n│   └── troubleshooting.md\n└── assets/               # 静态资源（模板文件、配置示例等）\n    └── (按需使用)\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003cstrong\u003e最关键的只有一个文件：\u003ccode\u003eSKILL.md\u003c/code\u003e\u003c/strong\u003e。它是 AI 加载 Skill 后读取的第一份文档，决定 AI 如何理解和使用这个 Skill。其余目录按需存在。\u003c/p\u003e","title":"WorkBuddy Skill 封装实战：以 QQ 群推送为例，将踩坑经验沉淀为可复用技能"},{"content":" ✅ 本文方案已于 2026-06-28 实际验证通过 — 两个 WorkBuddy 定时自动化任务（凌晨 2:00 工作纪要、3:00 早间新闻）均已成功通过 QQ 群推送。\n背景：自动化任务需要\u0026quot;最后一公里\u0026quot; 前面几篇文章分别解决了 Gitea 代码仓库、禅道项目管理、微信/企微数据读取 的 AI 接入问题。但这些解决的都是数据输入——让 AI 能读数据、查信息。\n一个真正有用自动化助手还需要输出通道。具体需求：\n「悟空的早间新闻推送」：每天凌晨 3:00 自动采集 AI 动态、天气、财经行情、ETF 监控，推送到手机 「温陵布衣昨日工作纪要」：每天凌晨 2:00 整理前一天的企微消息、邮件、项目日志，推送摘要 两个任务都跑得挺好，但消息一直只能写本地文件——你需要主动打开电脑去看，手机端完全收不到。这和\u0026quot;主动通知\u0026quot;的目标背道而驰。\n路线一：企业微信（失败） 最自然的思路是企业微信——公司全员在用，WorkBuddy 也有企微连接器（wecom connector）。\n尝试过程 企微连接器发消息：WorkBuddy 企微连接器已绑定成功，但发消息时报错 当前企业暂不支持授权机器人「消息」使用权限 管理员确认：咨询企业微信管理员，回复只有「文档」授权，没有「消息」授权 wecom-cli skill：这个 skill 本质是解密本地 SQLite 数据库，只读不写，无法发送消息 结论 企微消息推送的前提是企业管理员在企微后台开通「消息」权限。如果你的企业没有开通（多数企业内部出于安全管控不开放），企微推送这条路就彻底堵死了。无绕路方案。\n📎 如果你所在企业的企微已开通消息权限，WorkBuddy 企微连接器可以直接发消息，本文后续的 QQ Bot 方案不必看。\n路线二：QQ 机器人（成功） 受 OpenClaw 接入 QQ 机器人 的思路启发——OpenClaw 可以通过 QQ Bot 定时推送消息，WorkBuddy 的自动化任务同样可以。\n核心技术栈：\nWorkBuddy 自动化任务（凌晨执行） → 采集数据、生成消息文本 → 调用 Python 脚本 → botpy SDK 连接 QQ WebSocket 认证 → 发送群消息 → 手机 QQ 收到推送 完整实施步骤 第一步：在 QQ 开放平台注册机器人 打开 QQ 开放平台，用手机 QQ 扫码登录 点击「创建机器人」，填写名称（如 YourBot-WorkBuddy） 进入机器人详情 → 开发设置 → 获取以下两个凭据： 凭据 说明 AppID 纯数字，如 YOUR_APPID AppSecret 点击「生成」获取，只显示一次，立即保存 在「开发设置 → 权限配置」中开启 「群聊消息」 权限 确认机器人状态为 「已上线」（开发中的机器人无法调用 API） ⚠️ AppSecret 只显示一次。错过了只能「重置」，旧密钥立即失效。\n第二步：创建专用 QQ 群 在手机 QQ 创建一个新群（只有你一人即可），命名如 YourGroup-WorkBuddy 把刚注册的机器人拉进群 关键配置：群设置 → 机器人 → 打开 「机器人主动在群聊内发言」 为什么是群聊而不是私聊？QQ 官方 Bot API 对私聊有严格限制——每月只能主动发 4 条消息。群聊则无此限制（2026 年 6 月全量开放），每分钟可发 20 条。\n第三步：安装 botpy SDK 1 pip install qq-botpy botpy 是腾讯官方维护的 QQ Bot Python SDK，封装了 WebSocket 连接和 REST API。\n第四步：获取群标识（group_openid） QQ Bot API 发送群消息需要 group_openid，这不是群号，需要通过 WebSocket 事件获取。\n创建 qq_get_group_openid.py（下载）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; 获取 QQ 群的 group_openid 运行方式：把机器人加到群后，运行本脚本，然后在群里 @机器人 发一条消息 脚本会打印 group_openid 并保存到文件，然后自动退出 \u0026#34;\u0026#34;\u0026#34; import os import sys APPID = \u0026#34;YOUR_APPID\u0026#34; # 替换为你的 AppID SECRET = \u0026#34;YOUR_APPSECRET\u0026#34; # 替换为你的 AppSecret OUTPUT_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), \u0026#34;qq_group_openid.txt\u0026#34;) import botpy from botpy.message import GroupMessage class GetGroupOpenidClient(botpy.Client): async def on_group_at_message_create(self, message: GroupMessage): \u0026#34;\u0026#34;\u0026#34;收到群 @消息时触发\u0026#34;\u0026#34;\u0026#34; group_openid = message.group_openid print(f\u0026#34;GROUP_OPENID={group_openid}\u0026#34;, flush=True) # 退出脚本 import os as _os _os._exit(0) if __name__ == \u0026#34;__main__\u0026#34;: print(\u0026#34;=\u0026#34; * 50) print(\u0026#34;QQ Group openid Capture Script\u0026#34;) print(\u0026#34;=\u0026#34; * 50) print(f\u0026#34;AppID: {APPID}\u0026#34;) print(\u0026#34;In the QQ group, send an @message to the bot\u0026#34;) print(\u0026#34;=\u0026#34; * 50) intents = botpy.Intents.all() client = GetGroupOpenidClient(intents=intents, bot_log=False) client.run(appid=APPID, secret=SECRET) 运行方式：\n1 python qq_get_group_openid.py 脚本启动后，去手机 QQ 群 @机器人 发一条消息。脚本会打印 GROUP_OPENID=XXXXXXXX，同时保存到 qq_group_openid.txt。\n第五步：编写推送脚本 创建 qq_send_group_msg.py（下载）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; QQ 群消息发送脚本 - 通过 botpy WebSocket 连接发送群消息 用法： python qq_send_group_msg.py \u0026#34;消息内容\u0026#34; python qq_send_group_msg.py --file \u0026lt;文件路径\u0026gt; \u0026#34;\u0026#34;\u0026#34; import sys import os APPID = \u0026#34;YOUR_APPID\u0026#34; # 替换 SECRET = \u0026#34;YOUR_APPSECRET\u0026#34; # 替换 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) GROUP_OPENID_FILE = os.path.join(SCRIPT_DIR, \u0026#34;qq_group_openid.txt\u0026#34;) import botpy from botpy.message import GroupMessage class SendGroupMsgClient(botpy.Client): def __init__(self, **kwargs): super().__init__(**kwargs) self._msg_to_send = None self._group_openid = None async def on_ready(self): \u0026#34;\u0026#34;\u0026#34;WebSocket connected, send message then exit\u0026#34;\u0026#34;\u0026#34; if self._msg_to_send and self._group_openid: try: result = await self.api.post_group_message( group_openid=self._group_openid, msg_type=0, content=self._msg_to_send ) msg_id = result.get(\u0026#39;id\u0026#39;) if isinstance(result, dict) else result.id print(f\u0026#34;SEND_OK: id={msg_id}\u0026#34;, flush=True) except Exception as e: print(f\u0026#34;SEND_FAIL: {e}\u0026#34;, flush=True) else: print(\u0026#34;READY_OK: no message to send\u0026#34;, flush=True) os._exit(0) async def on_group_at_message_create(self, message: GroupMessage): print(f\u0026#34;GROUP_OPENID={message.group_openid}\u0026#34;, flush=True) def main(): if len(sys.argv) \u0026lt; 2: print(\u0026#34;Usage: python qq_send_group_msg.py \u0026lt;message\u0026gt;\u0026#34;) print(\u0026#34; python qq_send_group_msg.py --file \u0026lt;file_path\u0026gt;\u0026#34;) sys.exit(1) msg = sys.argv[1] if msg == \u0026#34;--file\u0026#34; and len(sys.argv) \u0026gt;= 3: filepath = sys.argv[2] if os.path.exists(filepath): with open(filepath, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: msg = f.read().strip() else: print(f\u0026#34;ERROR: File not found: {filepath}\u0026#34;) sys.exit(1) if not os.path.exists(GROUP_OPENID_FILE): print(\u0026#34;ERROR: qq_group_openid.txt not found\u0026#34;) print(\u0026#34;Run qq_get_group_openid.py first to capture group_openid.\u0026#34;) sys.exit(1) with open(GROUP_OPENID_FILE, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: group_openid = f.read().strip() print(f\u0026#34;Target: {group_openid} | Size: {len(msg)} chars\u0026#34;, flush=True) intents = botpy.Intents.all() client = SendGroupMsgClient(intents=intents, bot_log=False) client._msg_to_send = msg client._group_openid = group_openid try: client.run(appid=APPID, secret=SECRET) except Exception as e: print(f\u0026#34;ERROR: {e}\u0026#34;, flush=True) sys.exit(1) if __name__ == \u0026#34;__main__\u0026#34;: main() 测试发送：\n1 python qq_send_group_msg.py \u0026#34;[测试] WorkBuddy QQ推送通道测试\u0026#34; 如果手机 QQ 群收到消息，通道即打通。\n第六步：接入 WorkBuddy 自动化任务 在 WorkBuddy 自动化任务的 prompt 中，将「推送」步骤改为调用上述脚本：\n## 任务六：通过QQ机器人推送 调用 Python 脚本发送消息： python /path/to/qq_send_group_msg.py \u0026ldquo;\u0026lt;消息内容\u0026gt;\u0026rdquo;\n如果推送失败，将内容写入备用文件： /path/to/fallback_news.txt 注意事项：\n消息内容中不要使用 emoji（Windows GBK 控制台编码兼容问题） 消息内容中的双引号需要转义 建议在自动化 prompt 中指定 cwds 为脚本所在目录 五个踩坑实录 以下是实际过程中遇到的五个坑，排序按耗时从多到少。\n坑一：AppSecret 复制错误 现象：botpy 连接报错 100016 invalid appid or secret\n排查过程：\n检查 AppID 格式（字符串/数字都试过） 检查 AppSecret 是否有隐藏字符（空格、换行） 直接 curl API 测试 根因：QQ 开放平台的 AppSecret 生成和展示不在同一个页面。第一次从错误位置复制的旧密钥已失效。\n教训：获取 AppSecret 后立刻用 curl 验证：\n1 2 3 curl -s -X POST https://bots.qq.com/app/getAppAccessToken \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;appId\u0026#34;:\u0026#34;YOUR_APPID\u0026#34;,\u0026#34;clientSecret\u0026#34;:\u0026#34;YOUR_APPSECRET\u0026#34;}\u0026#39; 返回 access_token 即为有效。\n坑二：botpy SDK API 变化 现象：ImportError: cannot import name 'GroupMessage' from 'botpy.types.message'\n根因：botpy SDK 大版本更新后，类的位置变了：\n❌ 旧版：from botpy.types.message import GroupMessage ✅ 新版：from botpy.message import GroupMessage 同样，Intents 的构造方式也变了：\n❌ 旧版：botpy.Intents(botpy.Intents.DEFAULT_VALUE) ✅ 新版：botpy.Intents.all() 教训：遇到 SDK 文档不匹配时，直接读安装的包源码：\n1 2 pip show qq-botpy | grep Location # 找到安装路径 grep -r \u0026#34;class GroupMessage\u0026#34; \u0026lt;安装路径\u0026gt;/botpy/ # 找到类的真实位置 坑三：Windows 沙箱拦截文件写入 现象：脚本内部写文件时报 PermissionError\n根因：WorkBuddy 沙箱模式下，某些路径的写操作被拦截。\n解决方案：改为先打印到 stdout，再手动创建文件写入：\n1 print(f\u0026#34;GROUP_OPENID={group_openid}\u0026#34;, flush=True) # stdout 总能捕获 坑四：GBK 控制台编码导致 emoji 崩溃 现象：脚本打印 emoji 时崩溃 UnicodeEncodeError\n根因：WorkBuddy 自动化任务运行环境使用 GBK 编码控制台，emoji 字符无法 encode。\n解决方案：\n脚本中全部使用纯英文/ASCII 字符输出 消息内容中去掉 emoji，用纯文本符号代替 设置 PYTHONIOENCODING=utf-8 环境变量（如环境支持） 坑五：消息发送后脚本没有退出 现象：自动化任务执行后一直挂起不结束\n根因：botpy Client 的 on_ready 回调发送消息后没有主动退出进程，WebSocket 连接保持导致脚本不退出。\n解决方案：发送完成后调用 os._exit(0) 强制退出：\n1 2 3 4 async def on_ready(self): result = await self.api.post_group_message(...) print(f\u0026#34;SEND_OK: id={result.id}\u0026#34;, flush=True) os._exit(0) # 必须强制退出 方案对比 维度 企微推送 QQ Bot 群推送 前提条件 企业管理员开通「消息」权限 注册 QQ 开放平台机器人 开通难度 高（多数企业不开放） 低（个人即可操作） 发送限制 无 20 条/分钟 私聊推送 支持 每月仅 4 条 技术栈 WorkBuddy 内置连接器 botpy + 自定义脚本 手机端 企微 App QQ App 附录：企微推送失败全记录 企业微信推送尝试了以下三条路径，全部失败：\n尝试一：WorkBuddy 企微连接器 WorkBuddy 内置的企微连接器（wecom connector）在绑定企业微信后，文档读写正常，但发送消息时报错：\n当前企业暂不支持授权机器人「消息」使用权限 尝试二：企业微信管理后台 咨询企业微信管理员后确认：该企业的企微授权范围仅为「文档」，不包含「消息」。这是企业层面（而非用户层面）的权限管控，管理员本人也无法单独开通。\n尝试三：wecom-cli skill wecom-cli 的工作原理是解密本地企微客户端的 SQLite 数据库文件来读取聊天记录和联系人。它不具备发送消息的能力（没有调用企微消息 API）。\n结论 企微消息推送的三要素：企业授权 + 管理员配置 + 消息 API 权限。缺一不可。如果你的企业没有开通「消息」权限，不要在这条路上浪费时间，直接走 QQ Bot 方案。\n是否值得封装为 Skill？ 可以，而且建议。 将 QQ Bot 推送流程封装为 WorkBuddy Skill 有以下好处：\n一键安装：其他人只需安装 skill，无需手动下载脚本 统一配置：凭据和路径集中管理在 SKILL.md 中 自动化任务引用更简洁：只需 @skill:qq-bot-push --msg \u0026quot;内容\u0026quot; Skill 结构建议：\nqq-bot-push/ SKILL.md # 技能定义 + 配置说明 scripts/ qq_send_group_msg.py # 发送脚本（已脱敏占位） qq_get_group_openid.py # 获取 group_openid 脚本 后续我会把这一整套流程封装为一个 Skill，届时单独发布。\n需要替换的脱敏数据 ⚠️ 占位符替换说明 — 以下占位符需替换为你的真实值，真实值请自行妥善保存（建议存本地加密备忘录或密码管理器）。\n占位符 说明 获取方式 YOUR_APPID QQ 开放平台 AppID（纯数字） QQ 开放平台 → 机器人详情 → 开发设置 YOUR_APPSECRET QQ 开放平台 AppSecret QQ 开放平台 → 开发设置 → 点击「生成」获取（只显示一次） YOUR_GROUP_OPENID 目标 QQ 群的 openid 运行 qq_get_group_openid.py 后从 stdout 获取 YourBot-WorkBuddy 你在 QQ 开放平台注册的机器人名称 自定义 YourGroup-WorkBuddy 你自建的 QQ 群名称 自定义 附件下载 以下脚本文件可直接下载使用（已脱敏，替换占位符即可）：\n文件 下载 说明 qq_get_group_openid.py 下载 获取 QQ 群 group_openid（WebSocket 事件监听） qq_send_group_msg.py 下载 发送 QQ 群消息（WebSocket 连接+发送+退出） 附件中所有 YOUR_APPID、YOUR_APPSECRET 均为占位符，使用前需替换为真实值。完整 Skill 封装见后续文章 WorkBuddy Skill 封装实战。\n总结 打通 WorkBuddy 自动化任务的消息推送，核心结论：\n企微用不了就别纠结——没有消息权限就是没有，绕不过 QQ Bot 群推送是目前最可行的替代方案——个人即可注册，零成本 botpy 的 WebSocket 模式比纯 REST API 更可靠——QQ 平台要求先建 WebSocket 连接再发消息 踩过的坑都是好的——SDK API 变化、沙箱限制、编码问题，各花 5-10 分钟排查 ","permalink":"https://sikinzen.github.io/posts/workbuddyqqbotpush/","summary":"\u003cblockquote\u003e\n\u003cp\u003e✅ \u003cstrong\u003e本文方案已于 2026-06-28 实际验证通过\u003c/strong\u003e — 两个 WorkBuddy 定时自动化任务（凌晨 2:00 工作纪要、3:00 早间新闻）均已成功通过 QQ 群推送。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"背景自动化任务需要最后一公里\"\u003e背景：自动化任务需要\u0026quot;最后一公里\u0026quot;\u003c/h2\u003e\n\u003cp\u003e前面几篇文章分别解决了 \u003ca href=\"https://sikinzen.github.io/posts/howtoconnectgiteaandai/\"\u003eGitea 代码仓库\u003c/a\u003e、\u003ca href=\"https://sikinzen.github.io/posts/howtoconnectzentaoandai/\"\u003e禅道项目管理\u003c/a\u003e、\u003ca href=\"https://sikinzen.github.io/posts/howtoconnectwechatandai/\"\u003e微信/企微数据读取\u003c/a\u003e 的 AI 接入问题。但这些解决的都是\u003cstrong\u003e数据输入\u003c/strong\u003e——让 AI 能读数据、查信息。\u003c/p\u003e\n\u003cp\u003e一个真正有用自动化助手还需要\u003cstrong\u003e输出通道\u003c/strong\u003e。具体需求：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e「悟空的早间新闻推送」\u003c/strong\u003e：每天凌晨 3:00 自动采集 AI 动态、天气、财经行情、ETF 监控，推送到手机\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e「温陵布衣昨日工作纪要」\u003c/strong\u003e：每天凌晨 2:00 整理前一天的企微消息、邮件、项目日志，推送摘要\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e两个任务都跑得挺好，但\u003cstrong\u003e消息一直只能写本地文件\u003c/strong\u003e——你需要主动打开电脑去看，手机端完全收不到。这和\u0026quot;主动通知\u0026quot;的目标背道而驰。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"路线一企业微信失败\"\u003e路线一：企业微信（失败）\u003c/h2\u003e\n\u003cp\u003e最自然的思路是企业微信——公司全员在用，WorkBuddy 也有企微连接器（wecom connector）。\u003c/p\u003e\n\u003ch3 id=\"尝试过程\"\u003e尝试过程\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e企微连接器发消息\u003c/strong\u003e：WorkBuddy 企微连接器已绑定成功，但发消息时报错 \u003ccode\u003e当前企业暂不支持授权机器人「消息」使用权限\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e管理员确认\u003c/strong\u003e：咨询企业微信管理员，回复只有「文档」授权，没有「消息」授权\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e\u003ccode\u003ewecom-cli\u003c/code\u003e skill\u003c/strong\u003e：这个 skill 本质是解密本地 SQLite 数据库，\u003cstrong\u003e只读不写\u003c/strong\u003e，无法发送消息\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"结论\"\u003e结论\u003c/h3\u003e\n\u003cp\u003e企微消息推送的前提是\u003cstrong\u003e企业管理员在企微后台开通「消息」权限\u003c/strong\u003e。如果你的企业没有开通（多数企业内部出于安全管控不开放），企微推送这条路就彻底堵死了。\u003cstrong\u003e无绕路方案。\u003c/strong\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e📎 如果你所在企业的企微已开通消息权限，WorkBuddy 企微连接器可以直接发消息，本文后续的 QQ Bot 方案不必看。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"路线二qq-机器人成功\"\u003e路线二：QQ 机器人（成功）\u003c/h2\u003e\n\u003cp\u003e受 \u003ca href=\"https://www.appinn.com/openclaw-channel-qqbot/\"\u003eOpenClaw 接入 QQ 机器人\u003c/a\u003e 的思路启发——OpenClaw 可以通过 QQ Bot 定时推送消息，WorkBuddy 的自动化任务同样可以。\u003c/p\u003e\n\u003cp\u003e核心技术栈：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eWorkBuddy 自动化任务（凌晨执行）\n    → 采集数据、生成消息文本\n    → 调用 Python 脚本\n    → botpy SDK 连接 QQ WebSocket 认证\n    → 发送群消息\n    → 手机 QQ 收到推送\n\u003c/code\u003e\u003c/pre\u003e\u003chr\u003e\n\u003ch2 id=\"完整实施步骤\"\u003e完整实施步骤\u003c/h2\u003e\n\u003ch3 id=\"第一步在-qq-开放平台注册机器人\"\u003e第一步：在 QQ 开放平台注册机器人\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e打开 \u003ca href=\"https://q.qq.com/\"\u003eQQ 开放平台\u003c/a\u003e，用手机 QQ 扫码登录\u003c/li\u003e\n\u003cli\u003e点击「创建机器人」，填写名称（如 \u003ccode\u003eYourBot-WorkBuddy\u003c/code\u003e）\u003c/li\u003e\n\u003cli\u003e进入机器人详情 → \u003cstrong\u003e开发设置\u003c/strong\u003e → 获取以下两个凭据：\u003c/li\u003e\n\u003c/ol\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e凭据\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e说明\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eAppID\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e纯数字，如 \u003ccode\u003eYOUR_APPID\u003c/code\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eAppSecret\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e点击「生成」获取，只显示一次，\u003cstrong\u003e立即保存\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003col start=\"4\"\u003e\n\u003cli\u003e在「开发设置 → 权限配置」中开启 \u003cstrong\u003e「群聊消息」\u003c/strong\u003e 权限\u003c/li\u003e\n\u003cli\u003e确认机器人状态为 \u003cstrong\u003e「已上线」\u003c/strong\u003e（开发中的机器人无法调用 API）\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003e⚠️ AppSecret 只显示一次。错过了只能「重置」，旧密钥立即失效。\u003c/p\u003e","title":"WorkBuddy 自动化任务打通 QQ 消息推送：完整踩坑实录"},{"content":"背景 在\u0026quot;零端口暴露，5 分钟打通内网：Tailscale 内网穿透实战\u0026ldquo;和\u0026rdquo;蒲公英 vs Tailscale：贝锐蒲公英内网穿透实战与方案对比\u0026ldquo;两篇文章中，我已经把内网穿透跑通了——家里和公司电脑之间建立了一条低延迟的加密隧道。\n隧道通了之后，接下来要解决的实际问题是：从家里访问公司电脑上的文件夹，用什么方式最好？\n这篇文章实测对比四种主流方案，给出结论。\n四种方案速览 方案 一句话概括 需要穿透吗 向日葵远程文件传输 远程桌面软件自带的文件管理器，拖拽就能传 ❌ 自带穿透 Windows SMB 共享 把公司文件夹映射为本地的 Z: 盘 ✅ 依赖蒲公英/Tailscale Syncthing P2P 自动同步，两台机器互为镜像 ✅ 依赖蒲公英/Tailscale WebDAV HTTP 协议扩展，轻量级文件访问 ✅ 依赖蒲公英/Tailscale 方案一：向日葵远程文件传输 原理 向日葵本质是一个远程桌面工具，但内置了文件传输模块。连接远程电脑后，打开文件传输窗口就能双向操作两台机器的文件系统。\n笔记本 公司电脑 ┌──────────┐ ┌──────────┐ │ 向日葵客户端 │ ←── 加密隧道 ──→ │ 向日葵客户端 │ │ │ │ (被控端) │ │ 文件传输窗口 │ │ │ │ 拖拽/复制 │ │ │ └──────────┘ └──────────┘ 不需要任何额外的穿透工具——向日葵自带内网穿透，和蒲公英、Tailscale 是平行关系，而不是附属关系。\n核心功能 功能 说明 双向传输 笔记本 → 公司、公司 → 笔记本，拖拽文件即可 远程文件管理器 直接浏览公司电脑的目录结构，像本地资源管理器一样 断点续传 文件中断后自动继续，不用重新传 文件夹传输 支持整文件夹拖拽 共享文件夹 设置指定文件夹为共享态，多端可访问 传输速度 走向日葵自有协议，国内服务器加速，默认高速模式 优点 自带穿透：不需要蒲公英/Tailscale，一个向日葵搞定远程桌面 + 文件传输 上手最简单：拖拽文件，零配置，不需要设共享、不需要映射磁盘 功能最全：除了文件传输，还能远程桌面操作公司电脑（一站式解决） 速度快：国内服务器优化，共享文件夹模式下支持多端同步 权限控制：可设置只读/可写，适合团队场景 缺点 公司电脑必须开机且登录：向日葵是远程桌面，被控电脑锁屏或被注销就无法操作 免费版有限制：个人免费版带宽和传输速度受限，高速传输需付费 需要安装客户端：两端都要装向日葵 每次都要\u0026quot;远程\u0026quot;进去：不是常驻映射，每次操作都要先打开向日葵，不是\u0026quot;无缝\u0026quot;体验 方案二：Windows SMB 共享 原理 SMB（Server Message Block）是 Windows 原生的文件共享协议。把公司电脑上的文件夹设为共享，再通过蒲公英/Tailscale 的虚拟网络隧道，在笔记本上映射为网络驱动器（如 Z: 盘）。\n笔记本（Z: 盘） 蒲公英/Tailscale 隧道 公司电脑 ┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ \\\\10.168.x.x\\ │ ←──→ │ 加密隧道 │ ←──→ │ D:\\CompanyFiles │ │ CompanyFiles │ │ 延迟 20-50ms │ │ (共享文件夹) │ └─────────────────┘ └──────────────┘ └─────────────────┘ 搭建步骤 在公司电脑上：\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 1. 创建共享入口文件夹 mkdir D:\\CompanyFiles # 2. 右键 → 属性 → 共享 → 添加你的用户 → 勾选\u0026#34;读取/写入\u0026#34; # （或用命令行） net share CompanyFiles=D:\\CompanyFiles /grant:你的用户名,FULL # 3. 把散落各处的目录用符号链接汇聚 mklink /D \u0026#34;D:\\CompanyFiles\\项目文档\u0026#34; \u0026#34;D:\\Projects\\Docs\u0026#34; mklink /D \u0026#34;D:\\CompanyFiles\\技术资料\u0026#34; \u0026#34;E:\\TechDocs\u0026#34; # 4. 放行蒲公英/Tailscale 网段的 SMB（445 端口） netsh advfirewall firewall add rule name=\u0026#34;Allow SMB from VPN\u0026#34; dir=in action=allow protocol=TCP localport=445 remoteip=10.168.0.0/16 在笔记本上：\n1 2 # 映射为 Z: 盘（用公司电脑的蒲公英/Tailscale 虚拟 IP） net use Z: \\\\10.168.x.x\\CompanyFiles /persistent:yes 优点 无缝体验：映射后 Z: 盘就是本地盘——双击打开文件、Ctrl+S 保存，和在公司没区别 零额外软件：Windows 原生，不需要装任何东西 不占硬盘：按需访问，不消耗笔记本存储空间 权限精细：可设只读/读写/仅特定用户 缺点 依赖穿透：必须先有蒲公英或 Tailscale 打通隧道，SMB 本身不管穿透 高延迟下体验差：如果隧道速度慢（如 Tailscale relay 模式），小文件操作会有明显延迟 大文件慢：在网络盘上直接编辑大文件不如先拉到本地 需要初期配置：设共享、开防火墙、建符号链接、映射驱动器——首次部署 10-15 分钟 方案三：Syncthing P2P 同步 原理 Syncthing 在两台机器之间建立 P2P 连接，把你选中的文件夹自动同步到对方机器上。不是\u0026quot;远程访问\u0026rdquo;，而是\u0026quot;你有，我也有\u0026quot;。\n笔记本 蒲公英/Tailscale 隧道 公司电脑 ┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ D:\\CompanyFiles │ ←──→ │ 加密隧道 │ ←──→ │ D:\\CompanyFiles │ │ （本地副本） │ │ │ │ （主副本） │ │ │ │ 自动同步 │ │ │ └─────────────────┘ └──────────────┘ └─────────────────┘ 核心特性 特性 说明 双向同步 公司改了，笔记本自动更新；笔记本改了，公司也更新 版本保留 可配置保留 N 个历史版本，防误删 增量传输 只传改动部分，不是全量拷贝 选择性同步 可以只同步某个子目录，不必全量 优点 本地读写速度：文件就在笔记本硬盘上，打开、编辑都秒级响应 离线可用：同步完成后断网也能使用，联网后自动合并 备份属性：本质上也是备了一份在公司电脑上 开源免费：永久免费，无任何限制 缺点 占用本地硬盘：同步多少就占多少空间 初次同步慢：文件量大时，首次全量同步需要很长时间 冲突处理：两人同时改同一文件会产生冲突副本，需要手动合并 也依赖穿透：需要蒲公英/Tailscale 提供网络通路 方案四：WebDAV 原理 WebDAV 是基于 HTTP 协议扩展的文件管理协议。在公司电脑上搭建 WebDAV 服务端，笔记本通过 RaiDrive 等工具映射为网络驱动器。\n优点 比 SMB 更轻量，穿透性好 支持 HTTPS 加密 可通过浏览器直接访问 缺点 Windows 自带 WebDAV 客户端体验差（需用 RaiDrive 等第三方工具） 需额外搭建 WebDAV 服务端 目录浏览和搜索不如 SMB 综合体验不如前三种方案 七维度完整对比 维度 向日葵 SMB Syncthing WebDAV 部署难度 ⭐⭐⭐⭐⭐ 装客户端即用 ⭐⭐⭐ 需配共享+防火墙 ⭐⭐⭐ 装客户端+配同步 ⭐⭐ 需搭服务端 使用体验 ⭐⭐⭐⭐ 拖拽直观 ⭐⭐⭐⭐⭐ 像本地盘 ⭐⭐⭐⭐⭐ 纯本地速度 ⭐⭐⭐ 勉强可用 传输速度 ⭐⭐⭐⭐⭐ 自有加速 ⭐⭐⭐⭐ 取决于隧道 ⭐⭐⭐⭐ P2P 同步 ⭐⭐⭐ 取决于隧道 不占本地硬盘 ✅ ✅ ❌ 占硬盘 ✅ 自带宽敞穿透 ✅ ❌ 依赖蒲公英/Ts ❌ 依赖蒲公英/Ts ❌ 依赖蒲公英/Ts 远程桌面一体 ✅ 一站式 ❌ ❌ ❌ 离线可用 ❌ ❌ ✅ ❌ 免费程度 ⭐⭐⭐ 限速 ⭐⭐⭐⭐⭐ 完全免费 ⭐⭐⭐⭐⭐ 完全免费 ⭐⭐⭐⭐ 需工具 权限控制 ⭐⭐⭐⭐ 精细 ⭐⭐⭐⭐ 精细 ⭐⭐ 简单 ⭐⭐⭐ 一般 适合大文件 ⭐⭐⭐⭐⭐ 断点续传 ⭐⭐⭐ 一般 ⭐⭐⭐ 初同步慢 ⭐⭐ 差 适合小文件多 ⭐⭐⭐ 逐个传输 ⭐⭐⭐ 每个建连 ⭐⭐⭐⭐ 增量同步 ⭐⭐⭐ 选型建议 场景 推荐方案 ──────────────────────────────────────────────────── 主要用远程桌面，顺便传文件 → 向日葵（一站式） 看文档/Excel，偶尔编辑保存 → SMB（最像本地） 每天大量读写，离线也要用 → Syncthing 传输大文件/固件包 → 向日葵（断点续传+加速） 团队共享文件夹，权限管控 → 向日葵（企业版）或 SMB 零成本，已有穿透隧道 → SMB（原生免费） 我的选择与使用方式 主力方案：向日葵远程文件传输\n理由很直接：\n我已经有向日葵了（用来远程桌面操作公司电脑），文件传输是顺带功能 拖拽传文件比我之前用 Tailscale relay 模式下的 SMB 快得多 断点续传对大文件友好（固件包、编译产物几百 MB 到几 GB） 不需要额外配共享文件夹、放防火墙、映射驱动器——直接用 备用方案：SMB（通过蒲公英硬件网关隧道）\n等 SMB 搭好后作为补充：当不需要远程桌面、只想快速打开几个文档时，Z: 盘就是最方便的。两者不是互斥，而是互补。\n总结 问题 结论 哪种最快？ 向日葵（自有加速）+ Syncthing（本地读写） 哪种最方便？ 向日葵（拖拽即传）+ SMB（映射为本地盘） 哪种最省钱？ SMB / Syncthing（完全免费） 哪种功能最全？ 向日葵（远程桌面 + 文件传输 + 权限管理一体） 我选哪个？ 向日葵主力 + SMB 备用 内网穿透解决\u0026quot;怎么连\u0026quot;，文件访问解决\u0026quot;怎么用\u0026quot;。两条路都通了，才能真正实现\u0026quot;在家和在公司没区别\u0026quot;。\n系列文章 第一篇：零端口暴露，5 分钟打通内网：Tailscale 内网穿透实战 第二篇：蒲公英 vs Tailscale：贝锐蒲公英内网穿透实战与方案对比 第三篇：远程文件访问方案横评：向日葵 vs SMB vs Syncthing vs WebDAV（本文） ","permalink":"https://sikinzen.github.io/posts/remotefileaccesscomparison/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e在\u0026quot;\u003ca href=\"/posts/tailscaleintranetpenetration/\"\u003e零端口暴露，5 分钟打通内网：Tailscale 内网穿透实战\u003c/a\u003e\u0026ldquo;和\u0026rdquo;\u003ca href=\"/posts/oraypgyintranetpenetration/\"\u003e蒲公英 vs Tailscale：贝锐蒲公英内网穿透实战与方案对比\u003c/a\u003e\u0026ldquo;两篇文章中，我已经把内网穿透跑通了——家里和公司电脑之间建立了一条低延迟的加密隧道。\u003c/p\u003e\n\u003cp\u003e隧道通了之后，接下来要解决的实际问题是：\u003cstrong\u003e从家里访问公司电脑上的文件夹，用什么方式最好？\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e这篇文章实测对比四种主流方案，给出结论。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"四种方案速览\"\u003e四种方案速览\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e方案\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e一句话概括\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e需要穿透吗\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e向日葵远程文件传输\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e远程桌面软件自带的文件管理器，拖拽就能传\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e❌ 自带穿透\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eWindows SMB 共享\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e把公司文件夹映射为本地的 Z: 盘\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e✅ 依赖蒲公英/Tailscale\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eSyncthing\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eP2P 自动同步，两台机器互为镜像\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e✅ 依赖蒲公英/Tailscale\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eWebDAV\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eHTTP 协议扩展，轻量级文件访问\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e✅ 依赖蒲公英/Tailscale\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003chr\u003e\n\u003ch2 id=\"方案一向日葵远程文件传输\"\u003e方案一：向日葵远程文件传输\u003c/h2\u003e\n\u003ch3 id=\"原理\"\u003e原理\u003c/h3\u003e\n\u003cp\u003e向日葵本质是一个\u003cstrong\u003e远程桌面工具\u003c/strong\u003e，但内置了文件传输模块。连接远程电脑后，打开文件传输窗口就能双向操作两台机器的文件系统。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e笔记本                        公司电脑\n ┌──────────┐                ┌──────────┐\n │ 向日葵客户端 │  ←── 加密隧道 ──→ │ 向日葵客户端 │\n │            │                │  (被控端)   │\n │ 文件传输窗口 │                │            │\n │ 拖拽/复制  │                │            │\n └──────────┘                └──────────┘\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e不需要任何额外的穿透工具——向日葵自带内网穿透，和蒲公英、Tailscale 是平行关系，而不是附属关系。\u003c/p\u003e\n\u003ch3 id=\"核心功能\"\u003e核心功能\u003c/h3\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e功能\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e说明\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e双向传输\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e笔记本 → 公司、公司 → 笔记本，拖拽文件即可\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e远程文件管理器\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e直接浏览公司电脑的目录结构，像本地资源管理器一样\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e断点续传\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e文件中断后自动继续，不用重新传\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e文件夹传输\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e支持整文件夹拖拽\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e共享文件夹\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e设置指定文件夹为共享态，多端可访问\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e传输速度\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e走向日葵自有协议，国内服务器加速，默认高速模式\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"优点\"\u003e优点\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e自带穿透\u003c/strong\u003e：不需要蒲公英/Tailscale，一个向日葵搞定远程桌面 + 文件传输\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e上手最简单\u003c/strong\u003e：拖拽文件，零配置，不需要设共享、不需要映射磁盘\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e功能最全\u003c/strong\u003e：除了文件传输，还能远程桌面操作公司电脑（一站式解决）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e速度快\u003c/strong\u003e：国内服务器优化，共享文件夹模式下支持多端同步\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e权限控制\u003c/strong\u003e：可设置只读/可写，适合团队场景\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"缺点\"\u003e缺点\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e公司电脑必须开机且登录\u003c/strong\u003e：向日葵是远程桌面，被控电脑锁屏或被注销就无法操作\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e免费版有限制\u003c/strong\u003e：个人免费版带宽和传输速度受限，高速传输需付费\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e需要安装客户端\u003c/strong\u003e：两端都要装向日葵\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e每次都要\u0026quot;远程\u0026quot;进去\u003c/strong\u003e：不是常驻映射，每次操作都要先打开向日葵，不是\u0026quot;无缝\u0026quot;体验\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch2 id=\"方案二windows-smb-共享\"\u003e方案二：Windows SMB 共享\u003c/h2\u003e\n\u003ch3 id=\"原理-1\"\u003e原理\u003c/h3\u003e\n\u003cp\u003eSMB（Server Message Block）是 Windows 原生的文件共享协议。把公司电脑上的文件夹设为共享，再通过蒲公英/Tailscale 的虚拟网络隧道，在笔记本上映射为网络驱动器（如 Z: 盘）。\u003c/p\u003e","title":"远程文件访问方案横评：向日葵 vs SMB vs Syncthing vs WebDAV"},{"content":"前文回顾 上一篇文章记录了如何用 Tailscale 搭建内网穿透，实现对公司 Gitea、禅道和文件服务器的远程访问。\n搭建完成后，我顺手试了另一款国内常见的内网穿透工具——贝锐蒲公英。本文记录实际使用过程，并对两种方案做系统性对比。\n贝锐蒲公英是什么 蒲公英是贝锐科技（Oray）推出的 SD-WAN 组网产品，和 Tailscale 一样解决「远程访问内网」的问题，但技术路线不同：\n维度 Tailscale 蒲公英 纯软件组网 ✅ ✅（也有硬件版本） 核心卖点 零配置、WireGuard 加密 速度快、国内服务器、硬件加速 免费额度 3 用户 / 100 设备 免费版限制较多，主力是付费版 加速方式 软件打洞 + DERP 中继 硬件网关加速（购买硬件后） 实际使用过程 安装与初体验 注册贝锐账号（支持手机号 / 微信登录） 下载蒲公英客户端：https://pgy.oray.com/download/ 安装后打开，提示登录 到这里一切正常。\n踩坑：等待 RPC 服务启动失败 登录后客户端弹出提示：\n「等待 RPC 服务启动失败」\n客户端无法正常进入，反复重启无效。\n原因分析 电脑开机后，蒲公英软件自启动速度比 Windows RPC 服务启动快，PgyVPNService 依赖的 RPC 组件尚未就绪，导致启动时序冲突。\n解决方法（首选） 按 Win + R，输入 services.msc，回车 找到 PgyVPNService（蒲公英服务） 右键 → 属性 → 启动类型 改为 「自动（延迟启动）」 点击确定保存 重启电脑 使设置生效 仍无法解决？再排查以下两项 安全软件拦截：360 / 火绒 / 腾讯管家等可能延迟或拦截蒲公英服务启动。尝试暂时退出安全软件后重试。 虚拟网卡冲突：电脑上若有其他 VPN 软件（TeamViewer、OpenVPN 等）安装的虚拟网卡，可能产生冲突。可到「设备管理器 → 网络适配器」检查多余虚拟网卡，禁用或卸载后重试。 解决结果 按上述方法改为「自动（延迟启动）」并重启后，客户端正常启动，RPC 报错消失。✅\n蒲公英的内网穿透方式 纯软件组网（免费/试用） 和 Tailscale 类似，在每台需要组网的机器上安装客户端，登录同一账号，自动组建虚拟局域网。\n你的笔记本（外网） ↕ 蒲公英隧道（走贝锐国内服务器） 公司装有客户端的机器 ↕ 访问公司内网服务 优点：无需购买硬件，免费版可试用，国内服务器延迟低。\n缺点：免费版限速、限设备数，长期使用需付费。\n硬件网关加速（付费核心卖点） 这是蒲公英相比 Tailscale 最大的差异化优势：\n在公司网络出口处放置一台蒲公英路由器（硬件网关），它负责：\nNAT 穿透和隧道建立 硬件加速加密/解密（减轻客户端 CPU 负担） 智能选路（自动选择最优中继节点） 你的笔记本（外网） ↕ 蒲公英客户端 ↕ 贝锐国内加速节点（比 Tailscale DERP 快） ↕ 蒲公英硬件网关（公司出口） ↕ 公司内网服务 实测体验：在有硬件网关的情况下，速度明显优于 Tailscale 走境外 DERP 中继的场景。\n蒲公英 vs Tailscale 优劣势对比 维度 蒲公英 Tailscale 胜出方 速度（无硬件） 国内服务器，延迟低 境外 DERP 中继，延迟高 蒲公英 速度（有硬件） 硬件加速，体验最佳 仍依赖软件打洞 蒲公英 免费额度 免费版限制多（设备数/带宽） 3 用户/100 设备，完全够个人用 Tailscale 部署成本 软件免费，硬件需购买（数百~数千元） 纯软件，零成本 Tailscale 维护成本 低（贝锐托管） 极低（SaaS 全托管） 平手 安全性 贝锐自有协议 + AES 加密 WireGuard（业界标准）+ 自动密钥轮换 Tailscale（协议更透明） 数据主权 控制平面在国内（合规友好） 控制平面在境外（等保场景需评估） 视场景而定 NAT 穿透能力 强（国内服务器打洞成功率高） 极强（多种打洞策略 + DERP 兜底） Tailscale 跨平台支持 Windows / macOS / Linux / Android / iOS 同上 + 路由器 / NAS（更多平台） Tailscale 适用场景 企业需要合规 + 速度，且有预算买硬件 个人/小团队，零预算快速组网 视需求而定 选型建议 你是…… 推荐方案 ───────────────────────────────────────────────────── 个人/小团队，零预算 → Tailscale（免费版够用） 企业，有预算，对速度要求高 → 蒲公英 + 硬件网关 企业，有合规要求（数据不出境） → 蒲公英 或 Headscale 自建 出差为主，偶尔访问内网 → Tailscale（5分钟搞定） 固定多地办公室互联 → 蒲公英硬件方案（稳定性更好） 我的场景：个人远程访问公司内网，预算为零 → Tailscale 更合适。\n但如果公司有预算，且对访问速度有较高要求（比如频繁传输大文件），蒲公英硬件方案值得考虑。\n总结 项目 结论 蒲公英客户端体验 功能正常，但「RPC 服务启动失败」是个已知坑，需手动改服务启动类型 速度优势是否属实 ✅ 属实，国内服务器 + 可选硬件加速，比 Tailscale 走境外 DERP 快 最大缺陷 ❌ 免费版限制多，要获得好体验需要购买硬件设备 和 Tailscale 的关系 不是替代，是不同预算和场景下的不同选择 我的选择 继续用 Tailscale（零成本），等 UDP 41641 放行后速度会自动提升 遗留问题 Tailscale UDP 41641 放行后，实测 P2P 直连速度 Ubuntu 编译服务器加入 Tailscale 网络 蒲公英硬件网关实际部署体验和成本测算 等有进展后再整理后续文章。\n系列文章 第一篇：零端口暴露，5 分钟打通内网：Tailscale 内网穿透实战 第二篇：蒲公英 vs Tailscale：贝锐蒲公英内网穿透实战与方案对比（本文） 第三篇：远程文件访问方案横评：向日葵 vs SMB vs Syncthing vs WebDAV ","permalink":"https://sikinzen.github.io/posts/oraypgyintranetpenetration/","summary":"\u003ch2 id=\"前文回顾\"\u003e前文回顾\u003c/h2\u003e\n\u003cp\u003e上一篇文章记录了如何用 \u003cstrong\u003eTailscale\u003c/strong\u003e 搭建内网穿透，实现对公司 Gitea、禅道和文件服务器的远程访问。\u003c/p\u003e\n\u003cp\u003e搭建完成后，我顺手试了另一款国内常见的内网穿透工具——\u003cstrong\u003e贝锐蒲公英\u003c/strong\u003e。本文记录实际使用过程，并对两种方案做系统性对比。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"贝锐蒲公英是什么\"\u003e贝锐蒲公英是什么\u003c/h2\u003e\n\u003cp\u003e蒲公英是贝锐科技（Oray）推出的 SD-WAN 组网产品，和 Tailscale 一样解决「远程访问内网」的问题，但技术路线不同：\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e维度\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eTailscale\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e蒲公英\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e纯软件组网\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e✅\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e✅（也有硬件版本）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e核心卖点\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e零配置、WireGuard 加密\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e速度快\u003c/strong\u003e、国内服务器、硬件加速\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e免费额度\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e3 用户 / 100 设备\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e免费版限制较多，主力是付费版\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e加速方式\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e软件打洞 + DERP 中继\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e硬件网关加速\u003c/strong\u003e（购买硬件后）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003chr\u003e\n\u003ch2 id=\"实际使用过程\"\u003e实际使用过程\u003c/h2\u003e\n\u003ch3 id=\"安装与初体验\"\u003e安装与初体验\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e注册贝锐账号（支持手机号 / 微信登录）\u003c/li\u003e\n\u003cli\u003e下载蒲公英客户端：https://pgy.oray.com/download/\u003c/li\u003e\n\u003cli\u003e安装后打开，提示登录\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cstrong\u003e到这里一切正常。\u003c/strong\u003e\u003c/p\u003e\n\u003ch3 id=\"踩坑等待-rpc-服务启动失败\"\u003e踩坑：等待 RPC 服务启动失败\u003c/h3\u003e\n\u003cp\u003e登录后客户端弹出提示：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e「等待 RPC 服务启动失败」\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e客户端无法正常进入，反复重启无效。\u003c/p\u003e\n\u003ch4 id=\"原因分析\"\u003e原因分析\u003c/h4\u003e\n\u003cp\u003e电脑开机后，蒲公英软件自启动速度\u003cstrong\u003e比 Windows RPC 服务启动快\u003c/strong\u003e，\u003ccode\u003ePgyVPNService\u003c/code\u003e 依赖的 RPC 组件尚未就绪，导致\u003cstrong\u003e启动时序冲突\u003c/strong\u003e。\u003c/p\u003e\n\u003ch4 id=\"解决方法首选\"\u003e解决方法（首选）\u003c/h4\u003e\n\u003col\u003e\n\u003cli\u003e按 \u003cstrong\u003e\u003ccode\u003eWin + R\u003c/code\u003e\u003c/strong\u003e，输入 \u003ccode\u003eservices.msc\u003c/code\u003e，回车\u003c/li\u003e\n\u003cli\u003e找到 \u003cstrong\u003e\u003ccode\u003ePgyVPNService\u003c/code\u003e\u003c/strong\u003e（蒲公英服务）\u003c/li\u003e\n\u003cli\u003e右键 → \u003cstrong\u003e属性\u003c/strong\u003e → \u003cstrong\u003e启动类型\u003c/strong\u003e 改为 \u003cstrong\u003e「自动（延迟启动）」\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e点击确定保存\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e重启电脑\u003c/strong\u003e 使设置生效\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"仍无法解决再排查以下两项\"\u003e仍无法解决？再排查以下两项\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e安全软件拦截\u003c/strong\u003e：360 / 火绒 / 腾讯管家等可能延迟或拦截蒲公英服务启动。尝试暂时退出安全软件后重试。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e虚拟网卡冲突\u003c/strong\u003e：电脑上若有其他 VPN 软件（TeamViewer、OpenVPN 等）安装的虚拟网卡，可能产生冲突。可到「设备管理器 → 网络适配器」检查多余虚拟网卡，禁用或卸载后重试。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"解决结果\"\u003e解决结果\u003c/h4\u003e\n\u003cp\u003e按上述方法改为「自动（延迟启动）」并重启后，客户端正常启动，RPC 报错消失。✅\u003c/p\u003e","title":"蒲公英 vs Tailscale：贝锐蒲公英内网穿透实战与方案对比"},{"content":"背景 作为一家手机厂商的技术负责人，我日常需要处理以下场景：\n出差时访问公司内网的 Gitea 代码仓库（192.168.1.10:3000）和禅道项目管理系统（192.168.1.20:80） 在家办公时读写公司电脑上的各类文档和资料 远程 SSH 到公司内的 Ubuntu 编译服务器 公司的内网服务全部跑在 192.168.1.0/24 网段，没有一个暴露到公网。传统 VPN 方案存在配置繁琐、端口暴露、速度慢等问题。\n经过对比调研，我选择了 Tailscale 方案，全程搭建不到 5 分钟，零端口暴露，全部流量走 WireGuard 加密隧道。\n方案对比：为什么选 Tailscale？ 主流的四类内网穿透方案对比：\n维度 Tailscale frp ZeroTier 原生 WireGuard 底层技术 WireGuard TCP/UDP 反代 自研 VXLAN WireGuard 组网模型 Mesh P2P 客户端-服务端 Mesh P2P 点对点 NAT 穿透 ⭐⭐⭐⭐⭐ 多种策略+中继兜底 ❌ 需公网IP ⭐⭐⭐⭐ ⭐⭐ 需手动 部署难度 ⭐⭐⭐⭐⭐ 5分钟 ⭐⭐⭐ 需服务器 ⭐⭐⭐⭐ 较简单 ⭐⭐ 全手动 数据路径 打洞成功→直连 全经服务器 打洞成功→直连 纯直连 访问控制 ACL 细粒度规则 无 简单 无 免费额度 100 设备 / 3 用户 开源免费 25 设备 完全免费 Tailscale 的核心优势在于：把 WireGuard 的军用级加密和 mesh 组网能力包装成了\u0026quot;装客户端→登录→通\u0026quot;的傻瓜式体验。底层打洞策略极为激进，同时 DERP 中继作为兜底，确保总有路可走。\n我最终选择了 Tailscale SaaS 免费版，理由很直接：不需要维护服务器、自动密钥轮换、ACL 访问控制开箱即用。控制平面（密钥交换）在 Tailscale 的境外服务器上，但业务数据打洞成功后是端到端 P2P 直连，不经过任何第三方。\n架构设计 核心思路：一台机器当 Subnet Router，把整个公司内网网段\u0026quot;广播\u0026quot;到 Tailscale 虚拟网络。\n公司 Win10 办公电脑（Subnet Router） tailscale up --advertise-routes=192.168.1.0/24 → 把 192.168.1.0/24 暴露到 Tailscale 网络 你的笔记本（任何地方） tailscale up --accept-routes → 接受 Subnet Router 广播的路由 结果：笔记本上直接访问 192.168.1.10 → 流量自动走 Tailscale 隧道 → 公司 Win10 转发 → 实际内网服务 关键点：\n只开 Subnet Router，不开 Exit Node（不需要全局走公司代理） 所有流量经过 WireGuard 加密，即使走 DERP 中继也是加密的 访问内网服务就像在局域网一样，不需要改任何地址 搭建步骤 第一步：注册 Tailscale（2 分钟） 浏览器打开 login.tailscale.com/start 用 GitHub 账号 OAuth 登录（免费版支持 3 用户 / 100 设备） 登录后获得一个 tailnet，名字类似 yourname.github 第二步：在公司电脑和笔记本上安装客户端 下载 Tailscale for Windows，安装后用同一 GitHub 账号登录两台机器。\n装好后在笔记本上验证：\n1 2 3 tailscale status # 100.64.1.1 office-pc user@ windows - # 100.64.1.2 laptop user@ windows - 两台机器出现在同一个 tailnet 里就是通了。\n第三步：配置 Subnet Router 在公司 Win10 上，管理员 PowerShell：\n1 tailscale up --advertise-routes=192.168.1.0/24 然后去 Tailscale 管理后台，找到公司 Win10 那台设备，在路由设置里 Approve 192.168.1.0/24。\n在笔记本上接受路由：\n1 tailscale up --accept-routes 第四步：防火墙放行 在公司 Win10 上放行 Tailscale 网段的 ICMP 和 SMB：\n1 2 3 4 5 # 放行 ICMP（方便运维调试） netsh advfirewall firewall add rule name=\u0026#34;Allow Tailscale ICMP\u0026#34; protocol=icmpv4:8,any dir=in action=allow remoteip=100.64.0.0/10 # 放行 SMB 文件共享 netsh advfirewall firewall add rule name=\u0026#34;Allow SMB from Tailscale\u0026#34; dir=in action=allow protocol=TCP localport=445 remoteip=100.64.0.0/10 第五步（可选）：SMB 文件共享 如果需要远程读写公司电脑上的文件，在公司 Win10 上配置 SMB 共享：\n创建一个集中共享文件夹（如 D:\\CompanyFiles），右键 → 属性 → 共享 用符号链接把散落的目录汇聚进来： 1 2 mklink /D \u0026#34;D:\\CompanyFiles\\项目文档\u0026#34; \u0026#34;D:\\Projects\\Docs\u0026#34; mklink /D \u0026#34;D:\\CompanyFiles\\技术资料\u0026#34; \u0026#34;E:\\TechDocs\u0026#34; 在笔记本上映射为网络驱动器： 1 net use Z: \\\\100.64.1.1\\CompanyFiles /persistent:yes Z: 盘就是你全部的公司文件，双击秒开，就像在本地一样。\n验证 在笔记本上（断开公司网络的情况下）：\n1 2 3 4 5 6 7 8 9 10 11 # 测试 Gitea curl http://192.168.1.10:3000 # → HTTP 200，返回完整 HTML 页面 ✅ # 浏览器打开禅道 # http://192.168.1.20:80 ✅ # 查看连接状态 tailscale status # 如果显示 \u0026#34;direct\u0026#34; → P2P 直连，速度最佳 # 如果显示 \u0026#34;relay\u0026#34; → DERP 中继，速度稍慢 当前遗留问题 速度优化：DERP 中继 vs P2P 直连 目前两台机器之间的连接状态为 relay \u0026quot;lax\u0026quot;，意味着数据走的是一条厦门 → 洛杉矶 → 中国的路径，延迟约为 150-300ms。原因很直接：公司防火墙屏蔽了 UDP 出站端口 41641，Tailscale 的 P2P NAT 打洞失败，只能 fallback 到 DERP 中继。\n当前路径（relay）： 笔记本 ←→ 洛杉矶 DERP 中继 ←→ 公司 Win10 理想路径（P2P）： 笔记本 ←→ 公司 Win10（端到端直连） 解决方案很简单：让公司网管放行 Win10 的 UDP 出站端口 41641（目标地址不限）。放行后重启 Tailscale：\n1 tailscale down \u0026amp;\u0026amp; tailscale up --advertise-routes=192.168.1.0/24 打洞成功后连接会从 relay 变成 direct，P2P 直连延迟通常低于 5ms，所有服务的体验会有质的飞跃。\n这一步完成后，我会更新本文或发布后续文章，记录优化效果。\n文件访问的当下体验 在当前 relay 模式下：\n文件类型 体验 Word / Excel / PDF（\u0026lt; 10MB） 可用，打开有 2-5 秒延迟 小文件批量操作 较慢，每个文件都要建连 大文件（\u0026gt; 100MB） 建议先用 robocopy 拉到本地再操作 文件操作的核心瓶颈不在方案本身，而在网络路径。P2P 直连一旦建立，所有体验问题都会自动消除——不需要改任何配置。\n待接入设备 Ubuntu 编译服务器目前尚未接入。等两台 Windows 跑通并优化 P2P 直连后，编译服务器装上 Tailscale 客户端即可实现远程 SSH。\n总结 用 Tailscale 做内网穿透，我的核心感受是：易用性和安全性不矛盾。\n5 分钟搭建，零端口暴露，全部 WireGuard 加密流量。方案本身没有\u0026quot;坑\u0026quot;，唯一需要协调的是公司防火墙放行 UDP 出站——但这拿到 P2P 直连后，体验会是降维打击。\n如果你也需要类似场景，不妨试试。整套方案只用了 Tailscale 免费版 + Windows 原生功能，不花一分钱。\n系列文章 第一篇：零端口暴露，5 分钟打通内网：Tailscale 内网穿透实战（本文） 第二篇：蒲公英 vs Tailscale：贝锐蒲公英内网穿透实战与方案对比 第三篇：远程文件访问方案横评：向日葵 vs SMB vs Syncthing vs WebDAV ","permalink":"https://sikinzen.github.io/posts/tailscaleintranetpenetration/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e作为一家手机厂商的技术负责人，我日常需要处理以下场景：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e出差时\u003c/strong\u003e访问公司内网的 Gitea 代码仓库（\u003ccode\u003e192.168.1.10:3000\u003c/code\u003e）和禅道项目管理系统（\u003ccode\u003e192.168.1.20:80\u003c/code\u003e）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e在家办公时\u003c/strong\u003e读写公司电脑上的各类文档和资料\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e远程 SSH\u003c/strong\u003e 到公司内的 Ubuntu 编译服务器\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e公司的内网服务全部跑在 \u003ccode\u003e192.168.1.0/24\u003c/code\u003e 网段，没有一个暴露到公网。传统 VPN 方案存在配置繁琐、端口暴露、速度慢等问题。\u003c/p\u003e\n\u003cp\u003e经过对比调研，我选择了 \u003cstrong\u003eTailscale\u003c/strong\u003e 方案，全程搭建不到 5 分钟，零端口暴露，全部流量走 WireGuard 加密隧道。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Tailscale 内网穿透架构图\" loading=\"lazy\" src=\"/posts/tailscaleintranetpenetration/architecture.svg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"方案对比为什么选-tailscale\"\u003e方案对比：为什么选 Tailscale？\u003c/h2\u003e\n\u003cp\u003e主流的四类内网穿透方案对比：\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e维度\u003c/th\u003e\n\t\t\t\t\t\u003cth style=\"text-align: center\"\u003e\u003cstrong\u003eTailscale\u003c/strong\u003e\u003c/th\u003e\n\t\t\t\t\t\u003cth style=\"text-align: center\"\u003efrp\u003c/th\u003e\n\t\t\t\t\t\u003cth style=\"text-align: center\"\u003eZeroTier\u003c/th\u003e\n\t\t\t\t\t\u003cth style=\"text-align: center\"\u003e原生 WireGuard\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e底层技术\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003eWireGuard\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003eTCP/UDP 反代\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e自研 VXLAN\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003eWireGuard\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e组网模型\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003eMesh P2P\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e客户端-服务端\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003eMesh P2P\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e点对点\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eNAT 穿透\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e⭐⭐⭐⭐⭐ 多种策略+中继兜底\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e❌ 需公网IP\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e⭐⭐⭐⭐\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e⭐⭐ 需手动\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e部署难度\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e⭐⭐⭐⭐⭐ 5分钟\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e⭐⭐⭐ 需服务器\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e⭐⭐⭐⭐ 较简单\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e⭐⭐ 全手动\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e数据路径\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e打洞成功→直连\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e全经服务器\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e打洞成功→直连\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e纯直连\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e访问控制\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003eACL 细粒度规则\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e无\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e简单\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e无\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e免费额度\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e100 设备 / 3 用户\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e开源免费\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e25 设备\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: center\"\u003e完全免费\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e\u003cstrong\u003eTailscale 的核心优势在于\u003c/strong\u003e：把 WireGuard 的军用级加密和 mesh 组网能力包装成了\u0026quot;装客户端→登录→通\u0026quot;的傻瓜式体验。底层打洞策略极为激进，同时 DERP 中继作为兜底，确保总有路可走。\u003c/p\u003e","title":"零端口暴露，5 分钟打通内网：Tailscale 内网穿透实战"},{"content":" 📚 本文是「Obsidian 知识管理」系列第二篇。同系列：\n从印象笔记到 Obsidian：一次完整的笔记迁移实战 Obsidian 三设备同步实战：Windows 双机 Git + Android Syncthing 一台笔记本（移动办公） + 一台台式机（固定公司），怎么让两台电脑上的 Obsidian 笔记自动保持同步？这篇文章记录了用 GitHub 私有仓库 + Git Sync 插件实现的完整方案，包括配置步骤和 3 个踩坑实录。\n一、场景与需求 设备 场景 需求 台式机 公司固定使用 主力编辑，自动 push，每次打开自动 pull 笔记本 在家/公司/出差 随时读写，自动同步 核心要求：\n全自动：不需要手动 git push/pull 双向同步：任何一边的修改都能同步到另一边 安全：笔记内容不经过第三方服务器 最终选型：GitHub 私有仓库 + Git Sync 插件。\n二、为什么选 Git Sync 插件？ Obsidian 社区插件市场有好几个 Git 同步插件：\n插件 原理 评价 Git Sync（Livan Kumar） GitHub OAuth 授权，自动 commit + push + pull 推荐，配置最简单 Obsidian Git 调用系统 git 命令 功能强大但需手动配置 GitHub Sync (Multi-Platform) GitHub API 大文件限制 选择 Git Sync 的理由：\n用 GitHub OAuth，不需要手动创建 Token 自动创建私有仓库，零配置上手 Auto-sync 开关一键开启 三、安全分析：OAuth 真的安全吗？ 你可能会担心：插件不需要我提供 Token 就能访问我的 GitHub 仓库，数据安全吗？\n3.1 认证方式对比 方式 实现 安全等级 Git Sync OAuth 浏览器跳转 GitHub 授权，Token 存本地 ⭐⭐⭐⭐ 手动 PAT 自己生成 Personal Access Token ⭐⭐⭐⭐ 明文密码 不推荐 ❌ 两者的安全级别相当。OAuth 的优势是 GitHub 官方授权流程，权限范围明确（仅 repo），随时可以在 GitHub → Settings → Authorized OAuth Apps 中一键撤销。\n3.2 数据路径 Obsidian ←→ GitHub API ←→ GitHub 仓库 数据不经过任何第三方服务器。插件作者也看不到你的笔记。Token 只存本地插件目录。\n结论：Git Sync OAuth 方式足够安全，不需要手动 PAT。\n四、完整配置步骤 第 1 步：台式机（主力机）初始化 安装插件：\nObsidian → 设置 → 第三方插件 → 关闭受限模式 浏览 → 搜索 Git Sync（注意选作者 Livan Kumar 的） 安装并启用 连接 GitHub：\n设置 → Git Sync → 点 Connect to GitHub 浏览器自动打开 → 登录 GitHub → 授权 授权成功自动跳回 Obsidian 插件自动创建私有仓库（命名规则：obsidian-\u0026lt;vault名称\u0026gt;） 开启自动同步：\n设置 → Git Sync → 打开 Auto-sync 开关 下方设置防抖间隔（默认 3000ms，即修改停止 3 秒后自动提交） 此时台式机上的任何修改，停笔 3 秒后自动 push 到 GitHub。\n第 2 步：笔记本配置 不要在笔记本上创建空 Vault。正确的做法是先克隆远程仓库，再用 Obsidian 打开。\n① 克隆仓库到本地：\n1 2 # 确认 GitHub 仓库名（在台式机 Git Sync 设置里看 Repository 字段） git clone git@github.com:\u0026lt;你的用户名\u0026gt;/obsidian-\u0026lt;vault名\u0026gt;.git \u0026lt;你的本地笔记目录\u0026gt; 我用的是 SSH 克隆（需要提前配置 SSH key）。如果还没有，执行：\n1 2 3 ssh-keygen -t ed25519 -C \u0026#34;你的GitHub邮箱\u0026#34; cat ~/.ssh/id_ed25519.pub # 将输出内容添加到 GitHub → Settings → SSH and GPG keys ② Obsidian 打开文件夹：\n打开 Obsidian → 打开其他仓库 → 打开文件夹作为仓库 → 选择 \u0026lt;你的本地笔记目录\u0026gt;\n③ 安装 Git Sync 插件并授权：\n跟台式机步骤完全一样：安装 → Connect to GitHub → OAuth 授权 → 插件自动识别已有仓库 → 打开 Auto-sync。\n此时两台设备都连接到了同一个 GitHub 私有仓库。\n第 3 步：验证同步 在台式机上修改任意文件，等待 3 秒 去笔记本上刷新 Obsidian（或等插件自动 pull） 确认修改已同步 反过来：笔记本修改 → 台式机验证 双向同步确认成功后，日常使用中不需要任何手动操作。\n五、踩坑一：HTTPS 被公司防火墙封堵 现象 台式机上执行 git pull 报错：\nfatal: unable to access \u0026#39;https://github.com/...\u0026#39;: Failed to connect to github.com port 443 原因 Git Sync 插件用 HTTPS + OAuth Token 克隆仓库，但公司防火墙拦截了 443 端口对 GitHub 的出站连接。\n解决：把 remote 从 HTTPS 改为 SSH 1 2 cd \u0026lt;你的本地笔记目录\u0026gt; git remote set-url origin git@github.com:\u0026lt;你的用户名\u0026gt;/obsidian-\u0026lt;vault名\u0026gt;.git SSH 走 22 端口，只要你已配置好 SSH key，不受防火墙影响。\n验证：\n1 2 3 git remote -v # origin git@github.com:\u0026lt;你的用户名\u0026gt;/obsidian-\u0026lt;vault名\u0026gt;.git (fetch) # origin git@github.com:\u0026lt;你的用户名\u0026gt;/obsidian-\u0026lt;vault名\u0026gt;.git (push) 经验教训：公司网络环境下，GitHub HTTPS 很可能不通。用 SSH 替代是最简单的解法。提前配好 SSH key 能省很多折腾。\n六、踩坑二：文件锁定——插件和 git 命令互相抢占 现象 手动执行 git pull 时报错：\nUnlink of file \u0026#39;.git/objects/pack/...idx\u0026#39; failed. Should I try again? 即使关了 Auto-sync 也解决不了。\n原因 Git Sync 插件在 Obsidian 内部维护了一个 Git 进程，后台持续监控文件变化。即使关掉 Auto-sync，进程本身仍在运行，锁住了 .git 目录下的 pack 文件。\n手动 git pull 需要写入这些文件，但被插件进程占用，导致失败。\n解决方案：完全退出 Obsidian 后操作 关闭 Obsidian（不是最小化，是完全退出） 确认任务管理器中没有 Obsidian.exe 进程 再执行 git pull 手动同步完成后重新打开 Obsidian 或者 — 更彻底的方法 — 如果你确实是全新开始：\n1 2 3 4 # 备份当前目录 move \u0026lt;你的本地笔记目录\u0026gt; \u0026lt;你的本地笔记目录\u0026gt;_backup # 重新克隆 git clone git@github.com:\u0026lt;你的用户名\u0026gt;/obsidian-\u0026lt;vault名\u0026gt;.git \u0026lt;你的本地笔记目录\u0026gt; 经验教训：Git Sync 插件管理 Git 操作时，避免在 Obsidian 运行期间手动执行 git 命令。两者会争抢 .git 锁。日常使用无需手动 git 操作——插件的 Auto-sync 已经够用。\n七、踩坑三：重复仓库问题 现象 多次添加 Git Sync 插件后，GitHub 上出现了多个名字相似的仓库：\nobsidian-\u0026lt;vault名\u0026gt; obsidian-obsidian-\u0026lt;vault名\u0026gt; \u0026lt;vault名\u0026gt; 原因 每次清除配置再重新添加插件，插件都会\u0026quot;智能\u0026quot;地创建新仓库，但命名规则导致叠加。\n解决 在 GitHub 上删除所有多余仓库（Settings → Danger Zone → Delete this repository） 只保留一个 某台设备上重新配置 Git Sync 指向唯一的仓库 八、日常使用建议 场景 操作 日常编辑 不需要任何操作，3 秒自动同步 新电脑加入 git clone → 打开 Vault → 安装 Git Sync → OAuth 授权 冲突处理 极少发生（两台不同时编辑同一文件） 检查同步状态 Git Sync 面板显示最后同步时间 九、总结 核心方案：GitHub 私有仓库 + Git Sync 插件（OAuth 认证）\n关键教训：\nSSH 优于 HTTPS：公司防火墙环境下优先配置 SSH key 别手动 git 操作：插件和手动 git 会锁冲突，日常用插件就够了 重复仓库要清理：多次重配插件会产生多个仓库，手动删除多余的 这个方案的代价：30 分钟初始配置。收益：之后所有笔记自动同步，零维护。\n","permalink":"https://sikinzen.github.io/posts/obsidiansyncguide/","summary":"两台 Windows 电脑间 Obsidian 笔记自动同步的完整方案：GitHub 私有仓库 + Git Sync 插件，附带 HTTPS/SSH 切换和文件锁定等 3 个踩坑实录","title":"Obsidian 双机同步：Git Sync + GitHub 私有仓库全攻略"},{"content":" 📚 本文是「Obsidian 知识管理」系列第一篇。同系列：\nObsidian 双机同步：Git Sync + GitHub 私有仓库全攻略 Obsidian 三设备同步实战：Windows 双机 Git + Android Syncthing 印象笔记太臃肿了——这是我决定迁移的原始动力。70 多条笔记、91 张图片、4 个顶层目录，最终完整迁移到 Obsidian，三列宽表能正常显示，图片引用零断裂。这篇文章把完整流程和经验教训都记录下来。\n一、为什么要迁移 用印象笔记（中国版）多年，几个问题越来越难以忍受：\n客户端臃肿：启动慢、编辑卡顿、同步频繁失败 格式锁定：笔记内容绑定在私有格式里，无法用外部工具处理 迁移困难：新版 v10+ 甚至取消了标准 .enex 格式导出 不可编程：无法批量处理、格式化、自动化 选择 Obsidian 的理由很简单：纯 Markdown 文件，本地存储，用 Git 管理版本，完全可控。\n二、导出 ENEX：第一个坑 2.1 国内版 v10+ 无法导出 .enex 打开印象笔记 → 文件 → 导出 → 只有 .notes 格式可选，没有 .enex。\n这个 .notes 是 AES 加密的私有格式，无法用任何第三方工具解析。\n2.2 解决方案：安装旧版 Evernote v6.7.6 旧版国际版 Evernote 仍然支持 .enex 导出，而且能直接读取国内版的数据目录。\n操作步骤：\n下载 Evernote 6.7.6 安装包 安装后登录同一账号，笔记自动同步 全选笔记 → 文件 → 导出 → 格式选 ENEX 得到一个 Evernote.enex 文件（约几十 MB） 经验教训：迁移前先确认源工具是否支持标准格式导出。印象笔记的私有格式是最大的迁移障碍，好在这个坑有解——旧版客户端就是钥匙。\n三、ENEX 转 Markdown：Python 脚本批量转换 .enex 本质是 XML 文件，每条笔记包含 \u0026lt;title\u0026gt;（标题）和 \u0026lt;content\u0026gt;（HTML 正文）。核心工作是解析 HTML → 生成 Markdown。\n3.1 HTML 到 Markdown 的映射 印象笔记 HTML Markdown \u0026lt;h1\u0026gt; ~ \u0026lt;h6\u0026gt; # ~ ###### \u0026lt;b\u0026gt;/\u0026lt;strong\u0026gt; **文本** \u0026lt;a href=\u0026quot;...\u0026quot;\u0026gt; [文本](URL) \u0026lt;table\u0026gt;/\u0026lt;tr\u0026gt;/\u0026lt;td\u0026gt; Markdown 表格 \u0026lt;en-media hash=\u0026quot;...\u0026quot;\u0026gt; ![[图片名.png]] \u0026lt;en-todo checked=\u0026quot;true\u0026quot;\u0026gt; - [x] 任务 \u0026lt;div\u0026gt; 段落 \u0026lt;br\u0026gt; 换行 3.2 表格解析：最棘手的问题 印象笔记的表格是标准 \u0026lt;table\u0026gt; HTML，但很多笔记里表格被用于\u0026quot;布局\u0026quot;（比如多列信息），转换为 Markdown 表格时容易丢失结构。\n核心挑战：\n单元格内容路由：\u0026lt;td\u0026gt; 内的 \u0026lt;a\u0026gt;、\u0026lt;b\u0026gt;、\u0026lt;br\u0026gt; 等标签需要正确解析和内联 空单元格：HTML 的空 \u0026lt;td\u0026gt; 转换为 Markdown 时不能丢失列 粗体嵌套：\u0026lt;span style=\u0026quot;font-weight:700\u0026quot;\u0026gt; 不能简单当作 \u0026lt;b\u0026gt; 处理 最终方案是用 Python HTMLParser 子类，通过 _in_cell 状态标志统一管理单元格内容写入，配合 _append() 方法实现正确路由。\n3.3 图片引用映射 ENEX 中图片以 \u0026lt;en-media hash=\u0026quot;...\u0026quot; type=\u0026quot;image/png\u0026quot; /\u0026gt; 形式引用，实际图片文件在同一目录下以 hash 命名。转换时：\n从 ENEX 提取 \u0026lt;en-media\u0026gt; 的 hash 值 匹配对应的图片文件 将图片改为语义化命名（如 某楼盘-装修想法_4.png） Markdown 中用 Obsidian wiki-link 引用：![[图片名.png]] 四、自动分类：关键词 + 权威源校准 4.1 顶层目录结构 按照原来的印象笔记本结构，设计了 4 个顶层目录：\n个人笔记/ → 出行旅游、房屋装修、育儿笔记、个人理财、办事指南、通用 工作笔记/ → HMD、TCL、其他客户、人力资源、认证、需求整理、通用 学习笔记/ → AI、CustomerManager、通用 通讯录/ → 扁平结构 4.2 关键词自动分类 第一轮用 Python 脚本根据笔记标题/内容的关键词自动分配到对应子目录。但初次分类准确率只有约 70%，原因是：\n笔记标题可能不包含明确类别词 同一篇笔记可能涉及多个主题 4.3 权威源校准 后来在 \u0026lt;你的本地导出目录\u0026gt; 下发现了按笔记本分类导出的独立 ENEX 文件：\n个人笔记.enex → 27 条笔记 工作笔记.enex → 27 条 学习笔记.enex → 13 条 通讯录.enex → 7 条 这些文件的顶层分类是权威的——因为它们本身就是按笔记本导出的。用这些文件做了第二轮校准，分类准确率提升到 100%。\n经验教训：自动分类很美好，但最终要有一个权威源做校准。如果你的印象笔记还保留着原始目录结构，在导出时按笔记本分别导出，可以省去大量事后校准的时间。\n五、图片处理：语义命名 + 去重 + 集中管理 5.1 集中存放 所有图片统一放在 Vault 根目录下的 attachments/ 文件夹。Obsidian 设置中配置 attachmentFolderPath: \u0026quot;attachments\u0026quot;，全局搜索匹配。\n5.2 语义化命名 原始图片文件以 hash 命名（如 f3a1b2c3.png），完全不可读。转换为：\n某楼盘-装修想法_4.png ← 包含文档名 + 序号 北京全家游-行程表_1.png 好处：去重时一眼能看出归属，删除废弃文档时能顺手清理对应图片。\n5.3 图片去重 同一张图可能在多条笔记中引用。用 Python 脚本按 hash 去重，确保 attachments/ 里每张图只存一份。\n5.4 修复引用断裂 转换过程中出现过\u0026quot;所有图片引用都指向同一张图\u0026quot;的 bug。原因是重构脚本时错误地覆盖了图片映射表。修复方法：重新从原始 ENEX 提取每个文件的图片引用关系。\n经验教训：图片引用是迁移中最容易出错的环节。每轮格式修改后都做一次全量图片引用校验（![[ 匹配 + 文件存在性检查），避免累积错误。\n六、Markdown 格式化与清理 6.1 标题层级修复 ENEX 转换后的 Markdown 可能有：\n多余的 # 前缀叠加（如 ## ## 标题） 标题级别不统一 通过正则清理，确保每个 # 标题干净、层级合理。\n6.2 空行压缩 转换后部分文件有大量连续空行（最多的 15 行连续空白）。用脚本把连续空行压缩到最多 2 行，同时保护代码块区域不被误处理。\n6.3 表格重建 初始转换后，14 个文件的表格出现碎片化——单元格内容跑到表格外面去了。原因是 HTML 解析时 _pending_text 和 _current_cell 路由错误。\n修复方法：\n引入 _in_cell 布尔标志 所有文本写入通过 _append() 统一路由 \u0026lt;/span\u0026gt; 不触发 ** 闭合 修复后从 ENEX 源文件直接全量重建，零残留碎片。\n七、表格显示优化：CSS 片段 Obsidian 默认表格不会换行也不会滚动。对于多列表格（如 9 列的「北京全家游」），内容被推到屏幕外。\n七.1 两套方案并行 方案 适用场景 做法 A: 横向滚动 \u0026gt;= 5 列 overflow-x: auto + white-space: nowrap B: 单元格换行 \u0026lt;= 4 列 word-wrap: break-word + white-space: normal 七.2 实现方式 创建 table-wrap.css 放入 .obsidian/snippets/：\n1 2 3 4 5 6 7 8 9 10 11 /* 方案 B：默认所有表格单元格换行 */ .markdown-reading-view table td, .markdown-reading-view table th { white-space: normal; word-wrap: break-word; } /* 方案 A：带 cssclasses 标记的文件启用横向滚动 */ .table-scroll .markdown-reading-view .table-wrapper { overflow-x: auto; } 然后用 Obsidian 的 cssclasses frontmatter 功能，给 5 个多列表格文件加上 cssclasses: [table-scroll]，精确控制哪些文件用滚动方案。\n自动判断逻辑：扫描所有文件 → 统计最大列数 → \u0026gt;= 5 列加 table-scroll，其余默认换行。\n经验教训：不要一刀切。3 列表格换行很自然，9 列表格换行反而更乱。用 cssclasses 做文件级别的精细控制，比全局 CSS 优雅得多。\n八、MOC 导航文件 为每个顶层目录创建了 Map of Content（MOC）文件，作为导航入口：\nREADME.md → 仪表盘，链接到 4 个顶层目录 MOC 个人笔记 MOC.md 工作笔记 MOC.md 学习笔记 MOC.md 通讯录 MOC.md 每个 MOC 文件列出该目录下所有子目录和笔记链接。\n九、最终成果 指标 数值 笔记总数 74 篇 (.md) 图片总数 91 张 顶层目录 4 个 子目录 18 个 图片引用断裂 0 孤儿图片 0 表格碎片文件 0（全部重建） 含 cssclasses 文件 5 个 十、经验教训总结 导出前确认格式：国内版印象笔记 v10+ 不提供 ENEX 导出，提前下载旧版 v6.7.6 保留权威分类源：按笔记本分别导出 ENEX，比事后关键词分类准确得多 图片引用每轮校验：每做一轮格式处理就做一次全量图片引用检查 表格用原生 HTML 解析：不要用正则处理 \u0026lt;table\u0026gt;，HTMLParser 状态机是最稳妥的方案 CSS 胜过改内容：表格显示问题用 CSS 片段解决，不动文档内容 多列滚动、少列换行：用 cssclasses 做文件级精细控制 增量迭代不如全量重建：出错后与其打补丁，不如从原始 ENEX 全量重建——更快更干净 ","permalink":"https://sikinzen.github.io/posts/evernotetoobsidian/","summary":"从印象笔记迁移到 Obsidian 的完整实操指南，包含 ENEX 解析、自动分类、表格重建、CSS 优化等 9 个关键步骤","title":"从印象笔记到 Obsidian：一次完整的笔记迁移实战"},{"content":"一、1Panel 是什么？ 1Panel 是一个现代化的开源 Linux 服务器运维管理面板，由飞致云（FIT2CLOUD）公司出品，强调轻量化和高安全性。\n与宝塔面板的核心差异主要体现在：\n对比维度 1Panel 宝塔面板 底层架构 强调容器化（Docker/Kubernetes）部署应用（Nginx、MySQL 等全在容器中运行），隔离性好 当前支持虚拟化和直接安装在宿主机上，部分应用须安装为 RPM 包，传统 LNMP 对宿主机侵入更大 安全权限 默认禁止 SSH 直接 root 登录，Web 端与操作系统权限严格分离，安全性更高 安全设置依赖用户手动配置，但 Web 端权限较高且操作更直接 运维模式 所有运维操作（端口放行、日志查看等）均在 Web 界面完成 除 Web 界面外还可通过 bt 命令进行各种快捷操作 开源收费 开源免费程度高，付费主要集中在同步和增值服务 免费版功能足够，但企业级功能（防火墙/集群管理/负载均衡等）需付费 总结：如果你追求现代化运维、习惯 Docker 生态、看重开箱即用的安全隔离，推荐 1Panel；如果你需要快速搭建传统网站、习惯直接操作文件系统，宝塔目前生态更成熟。\n二、如何安装 1Panel 1. 环境准备 1 2 3 sudo apt update sudo apt upgrade sudo apt install wget curl lsof v1 版本安装（较老，仅供旧版参考）：\n1 curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh \u0026amp;\u0026amp; sudo bash quick_start.sh 卸载命令：\n1 sudo 1pctl uninstall 推荐使用 v2 版本安装：\n1 sudo bash -c \u0026#34;$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)\u0026#34; 2. 安装完成后 安装成功后会输出面板访问信息（以下为示例，请以实际输出为准）：\n外部地址：http://\u0026lt;your-server-ip\u0026gt;:\u0026lt;port\u0026gt;/\u0026lt;entrance-code\u0026gt; 内部地址：http://\u0026lt;your-lan-ip\u0026gt;:\u0026lt;port\u0026gt;/\u0026lt;entrance-code\u0026gt; 面板用户：\u0026lt;your-username\u0026gt; 面板密码：\u0026lt;your-password\u0026gt; 官网：https://1panel.cn\n项目文档：https://1panel.cn/docs\n仓库地址：https://github.com/1Panel-dev/1Panel\n官方论坛：https://bbs.fit2cloud.com/c/1p/7\n🔐 如果使用云服务器，需要在安全组中开放对应端口（安装向导会有提示）。\n三、1pctl 命令行工具 SSH 登录 1Panel 服务器后，执行 1pctl user-info 可获取安全入口（entrance）。\n安装成功后，使用 1pctl 命令进行管理维护：\n1 2 3 4 5 6 7 8 9 10 11 12 Commands: status [core|agent] 查看 1Panel 服务状态 start [core|agent|all] 启动 1Panel 服务 stop [core|agent|all] 停止 1Panel 服务 restart [core|agent|all] 重启 1Panel 服务 uninstall 卸载 1Panel 服务 user-info 获取 1Panel 用户信息 listen-ip 切换 1Panel 监听 IP version 查看 1Panel 版本信息 update 更新 1Panel 系统信息 reset 重置 1Panel 系统信息 restore 恢复 1Panel 服务 四、1Panel 学习资源 1. 官方资源 1Panel 的官方文档和社区生态已经比较完善，直接看官方是最高效的方式。\n官方文档：最全的安装、权限、应用商店使用及常见问题解答。地址：https://1panel.cn/docs/ 推荐配套视频：B站搜索\u0026quot;飞致云开源大屏\u0026quot;或\u0026quot;1Panel 视频合集\u0026quot; 官方论坛：适合解决实战中的具体问题。论坛同时发布官方公告、版本更新日志和社区活动。地址：https://bbs.1panel.cn/ GitHub 仓库：关注项目动态（源码、Issues、PR），查看 Releases 版本记录和 Roadmap，适合寻找高阶用法或提交反馈。地址：https://github.com/1Panel-dev/1Panel 2. 学习建议 先通读官方文档的\u0026quot;安装\u0026quot;和\u0026quot;快速上手\u0026quot;章节 在本地或测试 VPS 上实际部署一个 1Panel 遇到问题优先搜索官方论坛 关注 GitHub Release 了解最新功能变化 五、1Panel 常用应用 可以查看 1Panel 的应用商店文档：https://1panel.cn/docs/v2/user_manual/appstore/openresty/ ，其中大部分应用都有详细说明和安装指南。\n1. 应用商店 一键安装的开源应用合集。通过面板即可安装 WordPress（建站）、Halo（博客）、Waline（评论系统）等，无需手动配置数据库和运行环境，实现一键部署。\n2. OpenResty 作为反向代理与 Web 服务器，是安装后搭建网站应用（如 WordPress）的前置条件。功能强大且灵活，可用于管理网站、SSL 证书和负载均衡。\n3. MySQL / MariaDB 运行数据库。几乎所有动态网站和复杂应用都必须安装。1Panel 提供便捷的远程访问控制和备份恢复功能。\n4. Redis 高性能内存数据库。用于加速网站访问速度、缓存和会话存储，是 WordPress、Typecho 等程序实现加速的关键组件。\n5. Alist 统一管理网盘的挂载工具。支持挂载百度网盘、OneDrive 等多种存储，映射到服务器目录，通过 WebDAV 协议向其他应用（如 Infuse）提供访问，也可直接生成分享链接。\n6. Cloudflared（Argo Tunnel） 安全穿透隧道。如果你没有公网 IP 也不想暴露端口，可以借助 Cloudflare 的隧道能力安全地将服务暴露到公网，并享受 CDN 和 DDoS 防护。\n7. Portainer 容器管理工具。虽然 1Panel 自带 Docker 管理能力，Portainer 提供了更专业的容器编排与监控界面，适合需要频繁查看容器日志、监控资源占用的深度 Docker 用户。\n六、使用 1Panel 搭建 OpenClaw 安装 OpenClaw 参考 1Panel 官方文档安装 OpenClaw：OpenClaw - 1Panel 文档（https://1panel.cn/docs/）。\n安装后在访问 WebUI 时可能遇到如下提示：\n1 2 origin not allowed (open the Control UI from the gateway host or allow it in gateway.controlUi.allowedOrigins) 此时需要在配置中添加以下内容：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 \u0026#34;gateway\u0026#34;: { \u0026#34;auth\u0026#34;: { \u0026#34;mode\u0026#34;: \u0026#34;token\u0026#34;, \u0026#34;token\u0026#34;: \u0026#34;\u0026lt;your-gateway-token\u0026gt;\u0026#34; }, \u0026#34;bind\u0026#34;: \u0026#34;lan\u0026#34;, \u0026#34;controlUi\u0026#34;: { \u0026#34;allowedOrigins\u0026#34;: [ \u0026#34;http://127.0.0.1:18789\u0026#34;, \u0026#34;http://\u0026lt;your-lan-ip\u0026gt;:18789\u0026#34; ], \u0026#34;dangerouslyDisableDeviceAuth\u0026#34;: true }, \u0026#34;mode\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;port\u0026#34;: 18789, \u0026#34;trustedProxies\u0026#34;: [ \u0026#34;127.0.0.1/32\u0026#34; ] } 配置完成后，打开 OpenClaw 页面可直接看到一个对话测试窗口。如果 AI 能正常回复，说明 OpenClaw 已部署成功。\n","permalink":"https://sikinzen.github.io/posts/howtobuild1panel/","summary":"\u003ch2 id=\"一1panel-是什么\"\u003e一、1Panel 是什么？\u003c/h2\u003e\n\u003cp\u003e1Panel 是一个现代化的开源 Linux 服务器运维管理面板，由飞致云（FIT2CLOUD）公司出品，强调轻量化和高安全性。\u003c/p\u003e\n\u003cp\u003e与宝塔面板的核心差异主要体现在：\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e对比维度\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e1Panel\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e宝塔面板\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e底层架构\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e强调容器化（Docker/Kubernetes）部署应用（Nginx、MySQL 等全在容器中运行），隔离性好\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e当前支持虚拟化和直接安装在宿主机上，部分应用须安装为 RPM 包，传统 LNMP 对宿主机侵入更大\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e安全权限\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e默认禁止 SSH 直接 root 登录，Web 端与操作系统权限严格分离，安全性更高\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e安全设置依赖用户手动配置，但 Web 端权限较高且操作更直接\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e运维模式\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e所有运维操作（端口放行、日志查看等）均在 Web 界面完成\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e除 Web 界面外还可通过 \u003ccode\u003ebt\u003c/code\u003e 命令进行各种快捷操作\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e开源收费\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e开源免费程度高，付费主要集中在同步和增值服务\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e免费版功能足够，但企业级功能（防火墙/集群管理/负载均衡等）需付费\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e\u003cstrong\u003e总结\u003c/strong\u003e：如果你追求现代化运维、习惯 Docker 生态、看重开箱即用的安全隔离，推荐 1Panel；如果你需要快速搭建传统网站、习惯直接操作文件系统，宝塔目前生态更成熟。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"二如何安装-1panel\"\u003e二、如何安装 1Panel\u003c/h2\u003e\n\u003ch3 id=\"1-环境准备\"\u003e1. 环境准备\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt upgrade\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt install wget curl lsof\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003ev1 版本安装（较老，仅供旧版参考）：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo bash quick_start.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e卸载命令：\u003c/p\u003e","title":"1Panel 面板搭建与使用指南"},{"content":"一、OpenClaw 是什么？ OpenClaw 是一个开源项目，可以理解为个人 AI 代理平台，也被称为\u0026quot;AI 管家\u0026quot;或\u0026quot;数字管家\u0026quot;。\n它运行在你的电脑或服务器上，通过连接各种聊天工具（Discord、Telegram、WhatsApp、Signal 等）作为操作界面。其核心理念是让 AI 成为你的代理，去接触设备和数据并完成任务。\n主要能力：\n理解自然语言指令：能总结网页、查询价格、管理日历、创建待办、为你的代码写注释等 控制外部服务：通过连接各种 API（Spotify 放音乐、控制智能家居，甚至操作 Photoshop 等应用） 接入大模型：支持接入 OpenAI、Anthropic 等云端 API，也支持接入 Ollama 等本地模型，注重隐私 跨平台通信：能加入你的群组收发消息、执行命令并回复，相当于把你的 AI 助手带进了各种聊天终端 简单说，它就是一个开源、本地托管的\u0026quot;贾维斯\u0026quot;，让你通过自然语言直接与系统和应用交互。\nOpenClaw 的结构图可参考：7 天学习路线 —— https://openclaw101.dev/zh/\n二、OpenClaw 核心概念 1. Gateway（网关） Gateway 是 OpenClaw 的核心控制中枢，承担着\u0026quot;大脑\u0026quot;和\u0026quot;路由器\u0026quot;的双重角色。你可以把它理解为：一个能接收指令、调度任务、连接各种模型和服务的服务器。\n具体来说，Gateway 主要做这几件事：\n统一入口，连接多个聊天平台\nOpenClaw 支持 Discord、Telegram、WhatsApp、Signal 等多种聊天工具。Gateway 作为统一入口，同时连接所有这些平台的消息——无论你从哪个应用发指令，Gateway 都会收到并进行处理。\n调度工具和多步执行\n当你说\u0026quot;帮我查一下天气，然后在 Spotify 放首歌\u0026quot;，Gateway 会：\n理解意图 调用对应的功能模块（天气 API、Spotify 控制器） 将结果原路返回给你 管理 Agent 和会话\nGateway 维护着每个用户的会话状态和上下文，让 AI 能够连续回答问题，而不是每次都\u0026quot;失忆\u0026quot;地开启新对话。\n对接 AI 模型\nGateway 负责与 OpenAI、Anthropic 或本地 Ollama 等 AI 模型通信。它把用户指令发给模型，再把模型的回复转发到聊天平台。\n承载系统和功能\nOpenClaw 的功能通过\u0026quot;插件\u0026quot;来扩展，这些模块都在 Gateway 下运行。你可以通过配置来启用或禁用特定功能。\n运行时表现：\n启动一个 Gateway 服务（可以是后台进程或 Docker 容器） 配置好各聊天平台的 Token 和 Bot 密钥 Gateway 同时连接这些平台，开始处理消息 一句话总结： 如果把 OpenClaw 比作一个\u0026quot;智能管家\u0026quot;，Gateway 就是管家的\u0026quot;大脑和电话系统\u0026quot;——能同时接听微信、短信、对讲机，收到指令后判断该动嘴、动手还是放音乐，再把结果告诉对应的人。\n在配置文件中，Gateway 设置通常涉及：\ngateway.port：Gateway 绑定的本地端口 gateway.platforms：连接哪些聊天平台（discord、telegram 等） gateway.agents：连接哪些 AI Agent 在 1Panel 上部署 OpenClaw 时，通过对应的应用商店项即可完成，Gateway 是需要暴露端口并进行安全配置的核心服务。\n2. Agent（代理） Agent 是实际执行任务的\u0026quot;AI 大脑\u0026quot;。每个 Agent 都有独立配置，包括：\n使用哪个 AI 模型（GPT-4、Claude，或本地 Ollama 等） 系统提示词（定义它的角色和风格） 可用工具集（决定它能操作什么） 你可以创建多个 Agent，比如一个\u0026quot;程序员 Agent\u0026quot;、一个\u0026quot;家庭管家 Agent\u0026quot;。Gateway 根据消息来源和意图，将指令路由到对应的 Agent。\n3. Skill（技能） Skill 是 OpenClaw 的功能扩展单元，类似于\u0026quot;能力模块\u0026quot;。每个 Skill 都是 Agent 可以直接使用的一项能力：\n控制智能家居 记录笔记 发送邮件 操作日历 执行 Shell 命令 Skill 是模块化的，可以按需安装/卸载，甚至自己编写。对于开发者来说，Skill 就是一段定义了\u0026quot;描述、参数、执行逻辑\u0026quot;的代码。OpenClaw 社区提供了大量开箱即用的 Skill。\n4. Channel（通道） Channel 是 Agent 与用户之间的\u0026quot;对话接口\u0026quot;。Gateway 通过 Channel 连接各个聊天平台：\nDiscord Channel Telegram Channel WhatsApp Channel Signal Channel 以及 Slack、Matrix 等 每个 Channel 维护着对应平台的连接状态、消息收发和用户身份识别等功能。配置文件里涉及 Bot Token 的地方，就是在配置 Channel。\n5. Tool（工具） Tool 是比 Skill 更细粒度的功能单元。一个 Skill 可能包含多个 Tool：\n\u0026ldquo;天气查询\u0026rdquo; Skill 可能包含 get_current_weather 和 get_forecast 两个 Tool \u0026ldquo;浏览器控制\u0026rdquo; Skill 可能包含 navigate_to_url、click_element、extract_text 等 Tool Agent 在运行时，根据用户指令选择并调用合适的 Tool。Tool 通常有明确的输入输出定义，供 AI 模型理解和调用。\n6. Memory（记忆） OpenClaw 支持多种形式的记忆：\n短期记忆：当前会话的对话历史，存储在实时内存中 长期记忆：跨会话的持久化信息，如用户偏好、重要事实，通过向量数据库（Chroma、Pinecone 等）支持 这让 Agent 能够\u0026quot;记住你\u0026quot;，而不是每次都开启全新对话。\n7. Workflow（工作流） Workflow 是预定义的自动化流程，由多个步骤组成：\n\u0026ldquo;每日简报\u0026rdquo; Workflow：抓取新闻 → 提取摘要 → AI 总结 → 发送到指定频道 \u0026ldquo;代码审查\u0026rdquo; Workflow：拉取 PR → 运行 Lint → 生成摘要 → 评论到 GitHub Workflow 可以定时触发，也可以由指令触发，是 OpenClaw 实现自动化的核心机制。\n8. Provider（提供商） Provider 指 AI 模型的服务提供方：\nOpenAI（GPT 系列） Anthropic（Claude 系列） 本地 Ollama（Llama、Mistral 等开源模型） 或其他兼容 API 你可以配置多个 Provider，让不同 Agent 选用不同模型（比如简单任务用本地模型省钱，复杂任务用云端强模型）。\n9. Session（会话） Session 代表一次完整的交互上下文。每个用户在每个 Channel 中都有独立的 Session，用于：\n维护对话历史 存储用户状态（如\u0026quot;正在等待确认删除操作\u0026quot;） 支持多轮对话和复杂问题的逐步解决 Session 是 Agent 能进行\u0026quot;多轮对话\u0026quot;和\u0026quot;深入交互\u0026quot;的基础。\n10. 概念之间的关系 1 2 3 4 5 6 7 8 9 10 11 用户 → Channel（消息接发） ↓ Gateway（路由调度） ↓ Agent（AI 大脑） ↓ Skill / Tool（调用工具） ↓ Memory（读取记忆，可能触发 Workflow） ↓ 结果原路返回 三、OpenClaw 常用命令 1 2 3 4 5 6 7 8 9 10 openclaw skills list # 列出技能列表 openclaw plugins list # 列出插件列表 openclaw doctor # 系统诊断 openclaw status # 查看服务状态 openclaw gateway status # 查看 Gateway 服务状态 openclaw health # 健康检查 openclaw configure # 修改配置（模型、频道等） openclaw daemon restart # 重启后台进程 openclaw daemon logs # 查看后台日志 openclaw onboard # 初始化引导 四、OpenClaw 的\u0026quot;大脑\u0026quot;架构 详情可参考：https://openclaw101.dev/zh/day/3\n1. SOUL.md — 灵魂档案 SOUL.md 是 AI 的\u0026quot;性格说明书\u0026quot;。它定义你是谁、怎么说、做什么、不做什么。\n你不是聊天机器人。你在成为某个人。\n2. USER.md — 用户档案 USER.md 写了一部分给\u0026quot;你\u0026quot;（人）看，写了一部分给 AI 看。你对自己描述得越详细，AI 就越能帮到你。\n3. AGENTS.md — 代理手册 AGENTS.md 描述了 AI 的工作方式和操作规范。如果说 SOUL.md 是\u0026quot;我是谁\u0026quot;，AGENTS.md 就是\u0026quot;我怎么干活\u0026quot;。\n五、OpenClaw 核心技能详解 1. Agent Reach — 桌面 / 应用操控 OpenClaw 的 Agent Reach 技能主要解决 AI 如何突破纯对话的限制，实际操控外部设备与软件。换句话说，让 Agent 不再局限于\u0026quot;聊天窗口\u0026quot;，而是可以：\n操控图形界面：模拟鼠标键盘输入，直接控制本地或远程的应用程序、操纵系统设置 执行端到端任务：比如自动生成报表、批量处理信息、剪辑视频或编辑文档，通过邮件客户端发送出去 连接物联网设备：控制智能硬件（传感器、机械臂、智能家居中控），也可以通过插件执行复杂指令 使用场景就是打通 AI 的\u0026quot;信息\u0026quot;与房间里的\u0026quot;物理世界\u0026quot;，产生实际影响。需要注意，OpenClaw 的具体实现可能会随官方文档和版本迭代而变化，实际使用时以对应版本的技术说明为准。\n2. Agent Browser — 网页自动化 Agent Browser 技能可以理解为为 AI 配备了一个\u0026quot;隐形浏览器\u0026quot;，一个完全由 AI 来\u0026quot;看\u0026quot;和\u0026quot;操作\u0026quot;的浏览器，而不是调用网页 API——相当于 AI 长了一双在真实浏览器里看网页的眼睛。\n主要能力：\n页面导航与交互：输入网址、点击按钮、填写表格、翻页等 状态识别与判断：读取渲染后的页面内容（含动态加载的 Ajax 数据），判断是否包含特定文本或元素 自动化任务执行：自动登录网站、抓取搜索结果、提取社交媒体的动态信息、完成多步骤的订票或预约 底层通过内置浏览器引擎实现，支持执行 JavaScript、等待异步加载、处理登录态和 Cookie。典型的指令像是\u0026quot;打开某个网页、等两秒、然后提取某段文字\u0026quot;这样的多步操作。\nAgent Reach 与 Agent Browser 的分工：\n技能 定位 举例 Agent Reach 通用目标操控：模拟操作桌面应用 填写 Excel、控制操作系统 Agent Browser 专属浏览器操控：处理网页动态数据和交互 登录网站、抓取 Ajax 内容 简单说：一个负责\u0026quot;操控桌面\u0026quot;，一个负责\u0026quot;操控网页\u0026quot;。\n3. cron-scheduling — 定时任务 cron-scheduling 技能让 AI 能够定时、自动执行任务。类似于 Linux 系统的 cron 机制，让 Agent 具备时间感知能力，不再只会被动响应指令。\n主要功能：\n定时执行：在精确的节拍时间触发某件事，如\u0026quot;明天下午 3 点提醒我开会\u0026quot; 循环调度：按固定频率重复执行，如\u0026quot;每天上午 8 点抓取新闻\u0026quot;、\u0026ldquo;每小时分析一次日志\u0026rdquo; 任务管理：动态查看、删除、暂停已创建的定时任务 典型应用：\n自动邮件处理：每 30 分钟检查一次邮箱，对特定主题邮件自动回复 信息与报告：每天凌晨汇总当日新闻摘要，发送到指定联系人 系统维护操作：在预定时间通过 Agent Reach 打开备份程序，或通过 Agent Browser 登录系统抓取数据 需要配合持久化存储（防止重启丢失）和时区配置一起使用。\n与前两个技能的关系：cron-scheduling 负责\u0026quot;何时做\u0026quot;，Reach 和 Browser 负责\u0026quot;怎么做\u0026quot;。\n4. DeepReader — 文档深度解析 DeepReader 的核心价值是让 AI 能够深度阅读和理解复杂文档，而不止于普通文本提取能处理的内容。\n它主要解决：\n复杂格式解析：能解析 PDF（含扫描件）、Word、Excel、PPT 等文件，准确提取文本、图表数据以及复杂排版内容 富文本内容识别：识别文档中图片中的文字（OCR），解析图表结构，将非结构化内容转化为结构化数据 精准问答与引用：支持对超长文档进行问答检索，精确定位到相关段落，并提供准确的原始引用 与普通文本提取的区别：\n特性 普通提取 DeepReader 提取范围 仅纯文本 文本、表格、图像、排版信息 扫描件支持 不支持 支持 OCR 问答精度 粗糙 可精确定位到段落 典型使用场景：\n从百页财报 PDF 中提取关键数据并分析趋势变化 将扫描的合同图片转化为可检索的文本 处理上百页技术文档手册，回答关于某个配置参数的具体问题 与其他技能的关系： 如果说 Agent Reach 管\u0026quot;桌面\u0026quot;、Agent Browser 管\u0026quot;网页\u0026quot;、cron-scheduling 管\u0026quot;时间\u0026quot;，那 DeepReader 管的就是\u0026quot;静态文档\u0026quot;，为其他技能提供输入。例如：cron 定时抓取一份报告 → DeepReader 读取每日报表 → Agent Reach 将摘要填入 Excel。\n5. find-skills — 能力发现 find-skills 是一个元功能——让 AI 在运行时动态发现自己的能力清单，比如查询 DeepReader 是否已启用。\n它与 DeepReader 的协作关系：\nDeepReader 用于处理 PDF、Word、Excel 等复杂文档的具体功能 find-skills 是一个元功能——当 AI 需要处理文档时，会通过 find-skills 检查系统是否已安装 DeepReader；如果没有，AI 可以根据提示降级为简单文本提取，或建议用户安装该技能 实际价值：\n动态适配：AI 在不同设备/不同服务器上加载的技能集合可能不同，通过 find-skills 自检，可以避免直接调用不存在的技能而报错 自动化流程保障：在复杂工作流中（如定时抓取网页 → DeepReader 分析报告），可先通过 find-skills 验证所需能力是否就绪 一句话总结：DeepReader 是干活的工具，find-skills 是查看工具箱里有什么。前者处理文档内容，后者管理能力边界。\n6. token-optimizer — 对话压缩 token-optimizer 的核心价值是智能压缩对话中的 token 消耗，防止对话因为超出模型限制而中断，同时控制使用成本。\n主要功能：\n自动压缩对话历史\n当对话超过设定阈值时，自动对更早的对话轮次进行摘要提取，大幅减少 token 但保留关键信息 例如：10 轮对话 5000 token → 压缩成摘要 500 token 智能选择性裁剪\n在保证对话连续性的前提下，对中间部分进行关键信息提取 可选择性地忽略冗余内容：重复的代码块、无意义的日志输出 优先级管理\n为不同内容分配保留优先级：系统指令 \u0026gt; 用户重要信息 \u0026gt; 工具调用结果 \u0026gt; 历史对话 支持标记某些信息\u0026quot;永不压缩\u0026quot;（如用户明确要求保留的数据） 典型使用场景：\n长时间运行的客服机器人：避免每月对话因 token 超标而中断 处理大文件的 AI：DeepReader 读取财报后压缩提取，再进行下一步分析 自动化日报生成：每天自动抓取+分析，优化长期记忆，丢弃临时细节 注意事项：压缩会丢失一些细节，需要在成本与准确性之间权衡。通常提供\u0026quot;摘要 + 关键引用\u0026quot;模式，而非保留原句。\n7. skillhub-preference — 偏好存储 skillhub-preference 的核心价值是管理和持久化存储用户对不同技能的偏好设置，让 AI 能够\u0026quot;记住你喜欢怎么被服务\u0026quot;，并在后续操作中自动应用这些偏好。\n主要功能：\n偏好存储与查询\n记住用户对特定技能的设置，如：\u0026ldquo;使用 DeepReader 时默认提取摘要而非全文\u0026rdquo;、\u0026ldquo;Agent Browser 使用无头模式且不显示窗口\u0026rdquo; 支持按技能名称、用户 ID、会话 ID 等多维度查询 动态覆盖与继承\n用户可随时修改偏好，如\u0026quot;转为无头模式\u0026quot;不影响全局设置 支持层级结构：用户全局偏好 \u0026gt; 技能默认偏好 \u0026gt; 单次任务指定 跨会话持久化\n偏好存储在本地或云端，重启 Agent 后仍然有效 例如：你告诉 AI \u0026ldquo;以后 DeepReader 处理 PDF 时只提取前 10 页\u0026rdquo;，这个设置会一直生效直到你修改 典型使用场景：\n个性化设置：将 token-optimizer 的压缩阈值设为 3000 token（默认可能是 5000） 自动化流程默认行为：cron-scheduling 创建的定时任务默认时区设为 Asia/Shanghai 减少重复指令：无需每次都说\u0026quot;使用 Agent Browser 时超时设为 30 秒\u0026quot;，系统自动应用 8. stock-monitor-pro — 股票监控 stock-monitor-pro 是一个专用金融技能，用于实时监控股票行情，支持自定义多维监控指标并触发提醒或自动操作。\n注意：此技能不包含推荐股票的功能。\n核心能力：\n市场数据监控：A 股、港股、美股等主要市场的实时/延时数据 多维预警：支持价格突破、涨跌幅、成交量异动、换手率、RSI、MACD 等技术指标条件 自动响应：触发预警后可联动执行操作： Agent Browser：打开财经网站查看详情 Agent Reach：打开交易软件（需自行授权） cron-scheduling：定时生成日报 邮件/微信通知等 典型应用：\n\u0026ldquo;假如某股票 5 分钟内涨幅超过 3%，立刻通知我\u0026rdquo; \u0026ldquo;每天收盘后生成持仓股的涨跌幅和成交量总结，发送到邮箱\u0026rdquo; \u0026ldquo;茅台跌破 1500 元时，用 Agent Browser 查看当日新闻\u0026rdquo; 与其他技能的关系：stock-monitor-pro 是专用技能，聚焦金融监控；Reach、Browser、cron 等提供通用的执行能力。\n注意：需要接入数据源（行情 API），免费接口可能存在延迟，自动化交易下单需充分模拟测试。\n9. web-search — 网络搜索 web-search 的核心价值是让 AI 能够搜索互联网获取实时信息，弥补训练数据截止日期带来的知识盲区。\n主要功能：\n实时信息获取\n查询新闻动态、天气预报、股票行情、赛事比分等动态数据 示例：\u0026ldquo;今天北京天气如何？\u0026ldquo;\u0026ldquo;茅台最新股价？\u0026ldquo;\u0026ldquo;欧冠决赛比分？\u0026rdquo; 知识补充与验证\n获取训练数据中不存在或不够详细的信息 验证事实性陈述，给出时效性更强的回答 多源聚合\n从多个搜索引擎和数据源获取结果 支持限定特定来源 与其他技能的关系：\n技能 分工 web-search 调用搜索引擎 API，返回摘要和链接 Agent Browser 打开具体网页进行深度交互（登录、填表等） 简单说：web-search 是给 AI 装的\u0026quot;搜索接口\u0026rdquo;，问天气查股价；Agent Browser 是自己去浏览器里打开网页操作。\n典型使用流程：\n1 2 3 4 用户问：\u0026#34;2025 年诺贝尔经济学奖得主是谁？\u0026#34; → AI 调用 web-search，关键词\u0026#34;2025 Nobel Prize Economics\u0026#34; → 从多个来源确认结果 → 返回准确答案 注意事项：\n需要配置搜索 API 密钥 可限制时间范围（如\u0026quot;过去 24 小时内\u0026rdquo;） 支持站内搜索限定（如 site:bbc.com） API 调用通常按次计费，建议配合 token-optimizer 避免重复查询 10. email-connector — 邮件收发 email-connector 为 OpenClaw 提供邮件收发能力，让 AI Agent 可以直接管理电子邮件。\n主要功能包括：连接邮箱账户（IMAP/SMTP）、读取收件箱和特定文件夹中的邮件、发送和回复邮件、根据规则自动分类和处理邮件。\n11. file-system — 本地文件读写 file-system 技能的核心价值是让 AI 能安全地读写本地的文件和目录，是实现文件自动化管理的基础能力。\n主要功能：\n文件操作\n读取、写入、删除、移动、复制文件和目录 支持文本文件（TXT、JSON、YAML、CSV）和二进制文件（图片、PDF） 目录管理\n列出目录内容，支持递归遍历 创建、删除文件夹 获取文件元数据（大小、修改时间、权限等） 路径安全控制\n通过限定只允许访问特定目录（如 /workspace 或用户指定的文件夹），防止 AI 误删系统文件或泄密 支持设置白名单/黑名单路径 典型使用场景：\n持久化存储：保存 token-optimizer 压缩后的对话摘要、cron-scheduling 的运行日志 工作流中间数据：读取文件中的报告数据，传给 DeepReader 做进一步分析 配置管理：读取 skillhub-preference 存储的偏好设置文件 数据导出：将 web-search 的查询结果保存为 CSV 或 Markdown 文件 与其他技能的关系： file-system 是基础设施——Reach 操作图形界面，Browser 操作网页，DeepReader 分析文档内容，但它们读写文件时都依赖 file-system 或等效能力。\n安全注意事项：\n路径沙箱：默认只能访问工作目录（如 ./workspace），无法读取系统敏感文件 危险操作确认：删除文件夹或覆盖文件需要显式确认，通常需要人工授权 大小限制：读取超大文件（\u0026gt;500MB 的日志）可能被限制或分批读取 六、接入 QQ 机器人 参考教程：https://www.appinn.com/openclaw-channel-qqbot/\n准备工作 注册并获得 QQ Bot 的 AppID 和 AppSecret（以下为示例，请替换为真实值）：\n类型 AppID AppSecret 频道机器人-公域 \u0026lt;your-appid-public\u0026gt; \u0026lt;your-appsecret-public\u0026gt; 频道机器人-私域 \u0026lt;your-appid-private\u0026gt; \u0026lt;your-appsecret-private\u0026gt; 在 OpenClaw 中配置 QQ 频道 通过 1Panel 进入 OpenClaw 容器的终端，执行以下命令：\n1. 安装 QQBot 插件\n1 openclaw plugins install @tencent-connect/openclaw-qqbot@latest 2. 添加 QQ 频道绑定\n1 openclaw channels add --channel qqbot --token \u0026#34;\u0026lt;your-appid\u0026gt;:\u0026lt;your-appsecret\u0026gt;\u0026#34; 3. 重启 OpenClaw 服务\n1 openclaw gateway restart 注意：由于是通过 1Panel 的容器方式部署，执行命令时需要在 1Panel 中进入 \u0026ldquo;AI → 容器 → OpenClaw\u0026rdquo; 的终端执行，保证命令在正确的容器环境中运行。\n在 QQ 频道中 @ 机器人提问，如果能正常回复，就说明配置成功了。\n附录：1Panel 安装 OpenClaw 默认技能 1Panel 安装 OpenClaw 时默认会安装哪些技能？各文件的加载顺序（优先级从高到低）：\n1 2 3 4 5 项目根目录 /skills（当前项目） ~/.agents/skills ~/.openclaw/skills（用户安装的托管技能等） bundled skills（OpenClaw 安装包自带） skills.load.extraDirs 指定的目录 即：默认 = 当前版本自带 bundled + 以上目录中的所有文件。具体列表因版本而异，可通过 openclaw skills list 查看。\n学习资源 OpenClaw 七天学习路线：https://openclaw101.dev/zh/day/3 Gateway 配置详解：https://openclaw101.dev/zh/day/4 Skill 开发进阶：https://openclaw101.dev/zh/day/5 工作流与自动化：https://openclaw101.dev/zh/day/6 📎 附件下载 🖼️ image1.png (PNG, 180.4 KB) 🖼️ image2.png (PNG, 78.0 KB) 🖼️ image3.png (PNG, 41.3 KB) 🖼️ image4.png (PNG, 52.1 KB) 🖼️ image5.png (PNG, 69.3 KB) 🖼️ image6.png (PNG, 56.9 KB) 🖼️ image7.png (PNG, 59.0 KB) ","permalink":"https://sikinzen.github.io/posts/howtouseopenclaw/","summary":"\u003ch2 id=\"一openclaw-是什么\"\u003e一、OpenClaw 是什么？\u003c/h2\u003e\n\u003cp\u003eOpenClaw 是一个开源项目，可以理解为\u003cstrong\u003e个人 AI 代理平台\u003c/strong\u003e，也被称为\u0026quot;AI 管家\u0026quot;或\u0026quot;数字管家\u0026quot;。\u003c/p\u003e\n\u003cp\u003e它运行在你的电脑或服务器上，通过连接各种聊天工具（Discord、Telegram、WhatsApp、Signal 等）作为操作界面。其核心理念是\u003cstrong\u003e让 AI 成为你的代理，去接触设备和数据并完成任务\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e主要能力：\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e理解自然语言指令\u003c/strong\u003e：能总结网页、查询价格、管理日历、创建待办、为你的代码写注释等\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e控制外部服务\u003c/strong\u003e：通过连接各种 API（Spotify 放音乐、控制智能家居，甚至操作 Photoshop 等应用）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e接入大模型\u003c/strong\u003e：支持接入 OpenAI、Anthropic 等云端 API，也支持接入 Ollama 等本地模型，注重隐私\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e跨平台通信\u003c/strong\u003e：能加入你的群组收发消息、执行命令并回复，相当于把你的 AI 助手带进了各种聊天终端\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e简单说，它就是一个开源、本地托管的\u0026quot;贾维斯\u0026quot;，让你通过自然语言直接与系统和应用交互。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eOpenClaw 的结构图可参考：7 天学习路线 —— \u003ca href=\"https://openclaw101.dev/zh/\"\u003ehttps://openclaw101.dev/zh/\u003c/a\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"二openclaw-核心概念\"\u003e二、OpenClaw 核心概念\u003c/h2\u003e\n\u003ch3 id=\"1-gateway网关\"\u003e1. Gateway（网关）\u003c/h3\u003e\n\u003cp\u003eGateway 是 OpenClaw 的核心控制中枢，承担着\u0026quot;大脑\u0026quot;和\u0026quot;路由器\u0026quot;的双重角色。你可以把它理解为：一个能接收指令、调度任务、连接各种模型和服务的服务器。\u003c/p\u003e\n\u003cp\u003e具体来说，Gateway 主要做这几件事：\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e统一入口，连接多个聊天平台\u003c/strong\u003e\u003cbr\u003e\nOpenClaw 支持 Discord、Telegram、WhatsApp、Signal 等多种聊天工具。Gateway 作为统一入口，同时连接所有这些平台的消息——无论你从哪个应用发指令，Gateway 都会收到并进行处理。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e调度工具和多步执行\u003c/strong\u003e\u003cbr\u003e\n当你说\u0026quot;帮我查一下天气，然后在 Spotify 放首歌\u0026quot;，Gateway 会：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e理解意图\u003c/li\u003e\n\u003cli\u003e调用对应的功能模块（天气 API、Spotify 控制器）\u003c/li\u003e\n\u003cli\u003e将结果原路返回给你\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e管理 Agent 和会话\u003c/strong\u003e\u003cbr\u003e\nGateway 维护着每个用户的会话状态和上下文，让 AI 能够连续回答问题，而不是每次都\u0026quot;失忆\u0026quot;地开启新对话。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e对接 AI 模型\u003c/strong\u003e\u003cbr\u003e\nGateway 负责与 OpenAI、Anthropic 或本地 Ollama 等 AI 模型通信。它把用户指令发给模型，再把模型的回复转发到聊天平台。\u003c/p\u003e","title":"OpenClaw 入门指南：打造你的个人 AI 代理平台"},{"content":"股票投资入门培训手册 从零开始，稳健学习投资之道\n适合人群：完全零基础的投资新手\n整理日期：2026年6月\n📋 目录 第一章：认识股票——你买的究竟是什么？ 1.1 股票的本质 1.2 股票市场是什么？ 1.3 为什么要投资股票？ 第二章：核心概念——必须掌握的基础词汇 2.1 股价相关概念 2.2 估值指标（判断股票贵不贵） 2.3 财务健康指标 第三章：选股框架——如何找到好公司？ 3.1 自上而下分析法 3.2 基本面分析法（初学者检查清单） 3.3 四种主流选股框架对比 第四章：风险管理——比选股更重要的事 4.1 分散投资（不要把鸡蛋放在一个篮子里） 4.2 仓位管理 4.3 常见风险类型 4.4 止损与止盈 第五章：A股特有规则（中国股民必读） 5.1 交易规则 5.2 涨跌停限制 5.3 开户与税费 第六章：实操步骤——如何开始你的第一笔投资 6.1 开户流程 6.2 买入股票的流程 6.3 看盘与持仓管理 6.4 新手推荐路径 第七章：推荐学习资源 7.1 必读书籍 7.2 工具网站 7.3 实用工具推荐 ⚠️ 重要风险提示 第一章：认识股票——你买的究竟是什么？ 1.1 股票的本质 股票，简单来说，就是一家公司的「所有权凭证」。\n当一家公司需要资金扩张时，它可以把公司切割成无数小份，每一份就是一股股票，然后向公众出售。\n💡 举个例子：\n假设你和朋友合开了一家奶茶店，两个人各出一半钱，那你们各自拥有 50% 的股份。\n股票市场做的事情是一样的，只是规模更大、参与的人更多。\n💡 核心提示：\n购买一只股票，你就成为了那家公司的「小股东」——公司赚钱你有份，公司亏损你也有份。\n1.2 股票市场是什么？ 股票市场是买卖股票的场所，就像菜市场买卖蔬菜一样，只不过商品换成了各家公司的股份。\n主要股票市场 市场 全称 所在地 特点 上交所 上海证券交易所（SSE） 中国上海 A股主板，大型国企、金融股为主 深交所 深圳证券交易所（SZSE） 中国深圳 A股主板 + 创业板（成长股） 港交所 香港证券交易所（HKEX） 香港 港股，国际化程度高 纽交所 纽约证券交易所（NYSE） 美国纽约 全球最大，传统大型企业 纳斯达克 NASDAQ 美国 科技股为主，苹果/微软/英伟达等 A股的特殊说明 概念 含义 A股 中国境内上市、以人民币交易的普通股票 B股 以外币交易的股票（已逐渐边缘化） H股 在香港上市的中国公司（如腾讯控股 00700.HK） 创业板 深交所旗下，代码以 300 开头，成长型创新企业 科创板 上交所旗下，代码以 688 开头，硬科技企业 北交所 北京证券交易所，代码以 8 开头，中小企业 1.3 为什么要投资股票？ 历史数据显示，长期来看，股票是普通人资产增值最有效的工具之一。原因如下：\n原因 说明 跑赢通货膨胀 钱放银行会因通胀而贬值，优质股票长期能跑赢通胀 分享经济增长红利 买股票 = 买一篮子优秀企业，分享它们的成长 门槛低 相比房产、创业，股票投资门槛极低，几百元即可开始 流动性强 股票可以随时买卖，不像房产需要数月周转 📊 历史数据参考 资产类别 长期年化收益（参考） 现金/活期存款 0.3～1.5% 银行理财 3～5% 债券基金 4～7% 股票（长期） 8～12%（但波动大） 房产（中国核心城市） 5～10%（流动性差） ⚠️ 重要提醒：高收益伴随高风险。股票短期可能亏损 30～50%，必须用自己的闲钱投资！\n第二章：核心概念——必须掌握的基础词汇 2.1 股价相关概念 术语 解释 举例 股价 一股股票当前的市场价格 某股票现在卖 ¥50/股 涨跌幅 今日价格相比昨日收盘的变化百分比 今日涨 3%，昨天 ¥50，今天 ¥51.5 开盘价 当天交易开始时的第一笔成交价格 早上 9:30 的第一笔价格 收盘价 当天交易结束时的最后成交价格 下午 3:00 的最后价格 最高价/最低价 当天交易中出现过的最高/最低价 今天最高涨到 ¥55，最低跌到 ¥48 成交量 当天买卖的总股数 今天共成交 1000 万股 成交额 当天买卖的总金额 今天共成交 5 亿元 换手率 成交量 ÷ 流通股本 × 100% 换手率 5% = 今天有 5% 的股份被买卖 A股交易时间（重要！） 时段 时间 说明 集合竞价 9:15～9:25 接受委托，9:25 统一撮合成交（开盘价） 早盘 9:30～11:30 连续竞价，实时成交 午休 11:30～13:00 休市 午盘 13:00～14:57 连续竞价，实时成交 收盘集合竞价 14:57～15:00 14:57 进入收盘集合竞价，15:00 统一撮合 💡 提示：创业板（300开头）和科创板（688开头）还有 盘后定价交易（15:05～15:30）。\n2.2 估值指标（判断股票贵不贵） 估值指标帮你判断一只股票的价格是否合理，是选股的核心工具。\n指标 全称 含义 参考标准 P/E（市盈率） Price-to-Earnings 股价 ÷ 每股盈利，反映你为每1元利润付了多少钱 越低越便宜，但不同行业差异大 P/B（市净率） Price-to-Book 股价 ÷ 每股净资产，反映账面价值 \u0026lt; 1 可能被低估 P/S（市销率） Price-to-Sales 股价 ÷ 每股营收，适用亏损成长股 越低越好 PEG PE to Growth 市盈率 ÷ 盈利增速，衡量成长性价比 \u0026lt; 1 较具吸引力 股息率 Dividend Yield 每股分红 ÷ 股价，衡量分红回报 \u0026gt; 3% 较为可观 📌 P/E 市盈率详解（初学者最重要！） P/E = 股价 ÷ 每股收益(EPS) 举例： 某股票股价 = 100元 每股收益 = 5元 P/E = 100 ÷ 5 = 20倍 含义：你花100元买入，公司每年赚5元， 需要 20年 才能靠利润收回成本。 P/E范围 判断 说明 \u0026lt; 15倍 可能被低估 但要看行业和成长性 15～30倍 合理区间 大多数优质股票在此范围 \u0026gt; 30倍 可能偏高 除非高增长能支撑高估值 \u0026gt; 50倍 高风险 泡沫风险大，新手慎入 💡 提示：初学者最需要掌握的是 P/E（市盈率），它是最常见、最直观的估值工具。\n2.3 财务健康指标 指标 全称 公式 含义 ROE 净资产收益率 净利润 ÷ 净资产 × 100% 衡量公司用股东的钱赚钱的能力。巴菲特认为 ROE 连续多年 \u0026gt; 15% 是优质企业的标志 净利润率 — 净利润 ÷ 营业收入 × 100% 越高说明每赚100元收入，留下的利润越多 资产负债率 — 总负债 ÷ 总资产 × 100% 一般 \u0026lt; 60% 较为健康；过高说明公司借太多钱运营，风险大 自由现金流 — 经营现金流 － 资本支出 公司赚到的真实可用现金。「利润可以造假，现金流更难造假」，这是资深投资者非常重视的指标 📊 财务指标快速判断表 指标 健康标准 危险信号 ROE \u0026gt; 15%，且连续3年以上 \u0026lt; 10%，或大幅波动 净利润率 \u0026gt; 10% 持续下降，或为负 资产负债率 \u0026lt; 60% \u0026gt; 70%，且持续上升 自由现金流 为正，且稳定增长 为负，或大幅低于净利润 第三章：选股框架——如何找到好公司？ 3.1 自上而下分析法（宏观 → 行业 → 公司） 这是最系统的选股方法，分三个层次逐步缩小范围：\n第一步：宏观分析——看大环境 关注：经济周期、利率政策、政府政策导向 例如：降息周期有利于成长股；政策大力支持新能源，相关板块受益 第二步：行业分析——选赛道 关注：市场空间、增速、竞争格局 例如：找「风口行业」——市场空间大、增速快、竞争格局良好的赛道 近年热门赛道：人工智能、新能源、老龄化医疗、半导体等 第三步：公司分析——选龙头 关注：竞争壁垒、市场份额、盈利能力 例如：在好赛道里，优先选竞争壁垒最强的公司（行业龙头） 壁垒包括：品牌、技术专利、规模效应、用户粘性等 3.2 基本面分析法（适合初学者的检查清单） 在分析一家公司时，逐一回答以下问题：\n分析维度 需要回答的问题 理想答案 商业模式 公司靠什么赚钱？商业模式容易理解吗？ 简单清晰，能用一句话解释 竞争优势 为什么客户选它而不是竞争对手？ 有明显的护城河（品牌/技术/成本） 成长性 未来3-5年，收入和利润还能持续增长吗？ 增速 \u0026gt; 15%，市场空间足够大 财务健康 公司有没有大量负债？现金流是否充裕？ 负债率合理，自由现金流为正 管理层 创始人/管理层是否诚信、有执行力？ 有良好业绩记录，无负面丑闻 估值 当前股价贵不贵？是否有安全边际？ P/E 低于历史均值，或有折价空间 💡 实操建议：把以上6个问题做成一张 打分表，每只候选股票都打分，总分最高的优先考虑。\n3.3 四种主流选股框架对比 框架 核心逻辑 适合人群 代表人物 价值投资 买被市场低估的好公司，等待价值回归 稳健型、长期投资者 巴菲特、格雷厄姆 成长股投资 以合理价格买入高增速公司（GARP） 风险承受能力中等，关注科技/消费 彼得·林奇 赛道投资 先选好赛道，再选赛道内的龙头 有宏观判断能力的投资者 各大基金经理 量化投资 用数据模型批量筛选，减少主观偏见 数据分析能力强的投资者 西蒙斯 💡 提示：建议初学者从「价值投资 + 赛道框架」结合入手，先理解基本面，再学会看行业趋势。\n第四章：风险管理——比选股更重要的事 很多新手犯的最大错误，是把所有精力放在「选哪只股票」上，却忽视了风险管理。实际上，如何管理风险才是决定长期投资成败的关键。\n4.1 分散投资（不要把鸡蛋放在一个篮子里） 分散方式 说明 建议 行业分散 不要把所有资金集中在一个行业，避免行业性风险 持有 3～5 个不同行业的股票 个股分散 单只股票仓位建议不超过总资金的 20～30% 4万本金，单只股票最多买 8000～12000元 时间分散 不要一次性全仓，可以分批买入（定投策略） 分 3～5 次买入，每次间隔 1～2 周 资产分散 股票之外，可配置债券、基金、现金等 股票仓位不超过可投资资产的 50%（新手） 4.2 仓位管理 「仓位」指你用于投资股票的资金占总资产的比例。\n建议原则 只用「闲钱」投资 —— 绝不用生活费、急用金、借来的钱炒股 初学者建议股票仓位不超过可投资资产的 50% 市场估值高时降低仓位，市场低迷时可适当提高仓位 保留足够现金 —— 机会来临时有子弹加仓 仓位管理参考表 市场状态 建议仓位 说明 牛市高位（市盈率 \u0026gt; 30倍） 20～30% 风险大，轻仓观望 震荡市（市盈率 15～25倍） 40～60% 中等仓位，精选个股 熊市低位（市盈率 \u0026lt; 15倍） 60～80% 逐步建仓，分批买入 极端低估（市盈率 \u0026lt; 10倍） 80～100% 历史大底，满仓买入 4.3 常见风险类型 风险类型 说明 如何应对 市场风险 整体市场下跌，所有股票都受影响（如金融危机） 分散配置，长期持有，不要在恐慌时割肉 行业风险 某个行业政策收紧或技术颠覆（如教培行业打压） 了解行业政策，不要过度集中单一行业 个股风险 单一公司经营恶化、财务造假、管理层出问题 做好基本面研究，分散持股 流动性风险 某些小盘股买卖困难，想卖时卖不出去 尽量选择交易活跃的大中型股票 情绪风险 因恐惧或贪婪做出错误决策（追涨杀跌） 制定规则并坚守，避免情绪化操作 💡 提示：投资中最危险的不是市场波动，而是你自己的情绪。制定投资计划并严格执行，比任何技巧都重要。\n4.4 止损与止盈 概念 说明 建议规则 止损 提前设定亏损上限（如买入后亏损 10% 就卖出），防止亏损持续扩大 单只股票止损线：-8～-15% 止盈 设定盈利目标，到达目标时兑现部分利润，避免贪心反转 分批止盈：+20% 卖一半，+50% 再卖一半 纪律执行 设好规则就必须执行，不要因为「感觉会涨」就不止损 机械执行，不要犹豫 止损止盈实操建议 以买入价 = 100元 为例： 止损线：-10% → 90元（跌破就卖，不要幻想反弹） 止盈线1：+20% → 120元（卖出1/2，锁定利润） 止盈线2：+50% → 150元（再卖出剩余1/2，留1/4继续持有） ⚠️ 最重要的纪律：止损可以救你的命！ 很多投资高手一年可以亏损多次，但每次亏损都不超过 -10%，最终全年实现盈利。\n第五章：A股特有规则（中国股民必读） ⚠️ 本章非常重要！ A股有若干特殊规则，不了解会导致操作失误甚至损失。\n5.1 交易规则 规则 说明 注意事项 T+1 交易 当天买入的股票，下一个交易日才能卖出 不能当天买当天卖（防止过度投机） 最低买入单位 A股：1手 = 100股，只能按手买入 股价 50元的话，最少需要 50 × 100 = 5000元 涨跌幅限制 普通股票：±10%；ST股票：±5%；新股上市首日无限制 详见下文 交易费用 佣金（万2.5～万1.5）+ 印花税（卖出时0.1%）+ 过户费 买入时不收印花税 T+1 规则详解 假设你今天（周一）买入某股票： 周一：买入成功，但今天不能卖 周二：可以卖出 特殊情况： 如果周一是节假日，则是「T+2」甚至更久 💡 提示：T+1 制度意味着你当天买入后，如果股价大跌，无法当天卖出止损，所以买入前要慎重！\n5.2 涨跌停限制 股票类型 涨跌幅限制 说明 普通A股（沪市/深市主板） ±10% 每天最多涨10%，最多跌10% 创业板（300开头） ±20% 波动更大，风险更高 科创板（688开头） ±20% 波动更大，门槛更高（需50万资产） ST/*ST股票 ±5% 被特别处理的绩差股 新股上市首日 无限制（但有临时停牌机制） 风险极高，新手不建议参与 北交所股票 ±30% 波动最大，风险最高 涨跌停的影响 某股票昨日收盘价 = 100元，今日涨停价 = 110元（+10%） 如果开盘就涨停（110元）： - 你买不进（因为所有人都在抢着买，没人卖） - 涨停板可能被「封死」一整天 如果开盘就跌停（90元）： - 你卖不出（因为所有人都在抢着卖，没人买） - 跌停板可能被「封死」一整天 ⚠️ 风险提醒：涨停买不进、跌停卖不出，是A股的特有风险。尤其是跌停时无法止损，可能导致远超预期的亏损！\n5.3 开户与税费 开户流程 选择券商（华泰证券、中信证券、东方财富、国泰君安等） 下载券商APP，完成身份验证（需提供身份证、银行卡信息） 风险测评（系统会评估你的风险承受能力） 开户审核通过（通常1～2个工作日） 银行转账入金，即可开始买卖股票 交易费用详解 费用类型 收取方 费率 收取时机 佣金 券商 万2.5～万1.5（可协商） 买入 + 卖出均收取 印花税 国家 0.1%（仅卖出时收） 仅卖出时收取 过户费 中登公司 0.001%（买卖均收） 买入 + 卖出均收取 举例：买入10,000元股票，佣金万2.5 买入佣金：10,000 × 0.00025 = 2.5元 买入过户费：10,000 × 0.00001 = 0.1元 买入总费用：约 2.6元 卖出10,000元股票，佣金万2.5 卖出佣金：10,000 × 0.00025 = 2.5元 卖出印花税：10,000 × 0.001 = 10元 卖出过户费：10,000 × 0.00001 = 0.1元 卖出总费用：约 12.6元 一买一卖总费用：约 15.2元（占本金 0.15%） 第六章：实操步骤——如何开始你的第一笔投资 6.1 开户流程 步骤 操作 说明 1 选择券商 国内常见：华泰证券、中信证券、东方财富、国泰君安等 2 下载APP 完成身份验证（需身份证、银行卡信息） 3 风险测评 系统评估你的风险承受能力（会问你几个投资经验问题） 4 等待审核 通常1～2个工作日开通 5 银行转账入金 从银行卡转入资金到证券账户 6 开始交易 账户到账后即可开始买卖股票 港股/美股开户（如需） 券商 市场 特点 富途牛牛 港股 + 美股 界面友好，中文服务好 老虎证券 港股 + 美股 费率低，适合高频交易 雪盈证券 港股 + 美股 雪球旗下，社区氛围好 6.2 买入股票的流程 完整买入流程： 第1步：搜索股票代码或名称 - 如：000001（平安银行）或输入「平安银行」搜索 第2步：查看行情 - 了解当前价格、今日涨跌幅、成交量、市盈率等基本信息 第3步：分析基本面 - 确认是否符合选股标准（参考第三章的检查清单） 第4步：决定买入数量 - A股：每手 = 100股，只能按手买入 - 例如：平安银行现价 12元，买1手需要 12 × 100 = 1,200元 第5步：选择委托方式 - 【限价单】：自己指定价格，只有股价到达指定价格才成交（推荐） - 【市价单】：按当前市价立即成交（可能买在高位，不推荐新手使用） 第6步：确认下单 - 仔细核对：股票代码、买入价格、买入数量 - 点击「确认买入」，等待成交 第7步：设置止损（非常重要！） - 买入后立即设置止损价（如：-10%止损） - 部分券商支持「条件单」，可以自动触发止损 6.3 看盘与持仓管理 建议 说明 不建议频繁看盘 每天盯盘容易引发情绪化操作，建议每周看1～2次即可 定期回顾持仓 每季度回顾一次，检查公司基本面是否发生变化 学会看财务报告 每年4月（年报）、8月（半年报）是公司发布业绩的时间 关注重大新闻 政策变动、管理层变化、竞争对手动态 财务报告关键时间点 报告类型 发布时间 关注重点 年报 每年1～4月发布（截止4月30日） 全年业绩，最重要 一季报 每年4月发布 开年业绩趋势 半年报 每年7～8月发布 上半年业绩 三季报 每年10月发布 前三季度业绩 6.4 新手推荐路径 阶段 时间 目标 建议行动 入门期 前3个月 建立知识体系，了解市场运作 纸上模拟交易，不动真钱；读完本手册，精读1～2本经典书籍 实践期 3～12个月 小资金实战，积累经验 用小额资金（可承受全损）买2～3只股票，记录每次决策原因 成长期 1～3年 形成自己的投资体系 总结胜负原因，优化选股标准，适当扩大仓位 成熟期 3年以上 稳定盈利，复利增值 坚守体系，保持学习，不断迭代优化 💡 核心建议：初学者请一定从模拟交易开始！几乎所有券商APP都有「模拟炒股」功能，用虚拟资金练手，等熟悉了再投入真金白银。\n第七章：推荐学习资源 7.1 必读书籍 书名 作者 适合人群 核心内容 《聪明的投资者》 本杰明·格雷厄姆 所有投资者 价值投资圣经，巴菲特强烈推荐 《彼得·林奇的成功投资》 彼得·林奇 初学者 通俗易懂，适合入门 《股市真规则》 帕特·多尔西 进阶投资者 教你如何分析企业的竞争护城河 《巴菲特致股东的信》 沃伦·巴菲特 所有投资者 直接学习巴菲特的投资思维 《穷查理宝典》 查理·芒格 进阶投资者 多元思维模型，拓展投资视野 7.2 工具网站 网站 网址 功能 东方财富 eastmoney.com A股数据、财经新闻、财报查询 雪球 xueqiu.com 投资者社区，可关注优质投资人 英为财情 investing.com 全球市场数据 SEC EDGAR sec.gov/edgar 美股公司官方财报（英文） 通达信 tdx.com.cn 专业股票软件，行情数据精准 同花顺 10jqka.com.cn A股行情、资讯、模拟炒股 7.3 实用工具推荐 券商APP对比 券商 APP名称 特点 佣金 东方财富 东方财富 界面友好，资讯丰富，适合新手 万2.5（可谈到万1.5） 华泰证券 涨乐财富通 功能全面，系统稳定 万2.5 中信证券 中信证券 大券商，研报质量高 万2.5 国泰君安 国泰君安君弘 综合服务好 万2.5 辅助工具 工具 用途 Excel/WPS表格 记录交易明细、计算收益、制作持仓跟踪表 通达信/同花顺 查看K线图、技术指标、资金流向 雪球APP 关注优质投资者，学习投资思路 集思录 可转债、打新等专题讨论社区 ⚠️ 重要风险提示 股票投资有风险，入市需谨慎。历史收益不代表未来表现。 本手册仅供学习参考，不构成任何投资建议。 请在充分了解风险、具备相应承受能力的前提下进行投资。 重大投资决策建议咨询持牌专业投资顾问。 新手最容易犯的10个错误 错误 说明 如何避免 1. 用生活费炒股 亏了会影响生活 只用闲钱投资 2. 满仓操作 没有现金应对回调 保留至少30%现金 3. 不止损 亏损持续扩大 严格设置止损线 4. 追涨杀跌 情绪化操作 制定计划，机械执行 5. 听消息炒股 盲目跟风 只买自己研究过的股票 6. 频繁交易 手续费侵蚀收益 减少操作频率 7. 重仓单只股票 风险过度集中 单只股票不超过20%仓位 8. 不会看财报 买了劣质公司 学懂基本财务指标 9. 期望过高 想一夜暴富 接受长期年化8～12%的合理收益 10. 不学习 靠运气赚钱 持续学习，不断完善投资体系 📝 总结：给新手的一句话 投资不是赌博，而是一门需要持续学习的生意。\n用闲钱投资，严格控制风险，保持耐心和长期视角，\n时间会是你最好的朋友。\n祝你投资顺利，稳健成长！\n本手册基于《股票投资入门培训手册》整理，结合A股市场实际规则补充完善。\n整理日期：2026年6月 | 适用市场：中国A股\n","permalink":"https://sikinzen.github.io/posts/introductiontostockinvestment/","summary":"股票投资入门完整培训手册，包含7大章节、A股特有规则详解、新手最容易犯的10个错误，帮助零基础投资者建立系统化的投资知识体系。","title":"股票投资入门培训手册——从零开始学投资"},{"content":"背景：为什么要把禅道接入 AI？ 前两篇写了个人微信和企业微信接入 AI 的方案，解决的是沟通数据的 AI 化问题。但在日常工作中，还有一个信息密度极高的系统——禅道。\n项目进度、Bug 追踪、需求管理、任务分配……这些结构化的项目管理数据如果能让 AI 直接读取和操作，就可以：\n🐛 快速查看和处理 Bug（\u0026ldquo;帮我看看今天新提的 S1 Bug\u0026rdquo;） 📋 跟踪项目进度（\u0026ldquo;XX 项目当前完成度如何？\u0026quot;） 🔍 全局搜索（\u0026ldquo;搜索所有关于 XX 的需求\u0026rdquo;） 📝 自动创建和分配任务（\u0026ldquo;帮我建一个 Bug，标题 XX，指派给 XX\u0026rdquo;）⚠️ 方案一受限，见下文 📊 汇总项目数据（\u0026ldquo;统计本月各项目的 Bug 数量\u0026rdquo;） 本文记录两种将禅道接入 AI 的完整方案，不覆盖、不替代，请根据实际需求选择：\n方案一 方案二 方式 ZenTaoMcp MCP Server zentao-cli Web 登录 认证 App 认证（APP_CODE + APP_KEY） Web 登录（账号密码 Session） 读取 ✅ ✅ 写入 ❌ 静默失败 ✅ 完整读写 部署难度 ⭐⭐⭐（需编译 Go） ⭐（Python 脚本，免编译） 推荐场景 只读场景、多用户共享 需要写入、个人使用 🔴 重要更新（2026-06-09）：新增方案二（zentao-cli），解决方案一无法写入的根本问题。两种方案各有优劣，请根据场景选择。\n先说结论：两种接入方案对比 对比项 方案一：ZenTaoMcp (MCP) 方案二：zentao-cli (Web 登录) 核心思路 编译 Go 程序作为 MCP Server，通过 MCP 协议连接 Python 脚本模拟 Web 登录，直接调用禅道表单接口 认证方式 App 认证（APP_CODE + APP_KEY） Web 登录（账号密码 → Session Cookie） 读取能力 ✅ 542 个工具，53 个模块全覆盖 ✅ 项目/任务/Bug/迭代等常用模块 写入能力 ❌ App 认证下静默失败 ✅ 完整读写（Session 有用户身份） 创建子任务 ❌ 缺 parent 参数 + 路径 Bug ✅ 两步法可创建（详见后文） 工具数量 542 ~10（按需扩展） 部署难度 ⭐⭐⭐（需 Go 环境 + 编译 + MCP 配置） ⭐（Python 脚本，免编译） 数据隔离 多人共用一个 App 每人用自己的账号，天然隔离 凭据安全 APP_KEY 长期不变 密码仅首次登录，后续用 Session 稳定性 ⭐⭐⭐（MCP 进程，自动重连） ⭐⭐（Session 过期需重新登录） 可扩展性 ⭐⭐⭐（542 工具全覆盖） ⭐⭐（需手写 Python 扩展） 适用场景 纯读取、多人共享 需写入、个人使用 如何选择？ 只需要读取禅道数据 → 选方案一（MCP），功能全面、稳定性好 需要创建/修改任务 → 必须选方案二（CLI），方案一写入静默失败 两者都要 → 方案一 + 方案二并行，读取走 MCP（542 工具覆盖广），写入走 CLI（Session 有用户身份） 方案一内部的 MCP Server 选型 方案一（MCP 方式）内部还有几种实现可选：\n对比项 ZenTaoMcp (Go) zentao-mcp-server (Node.js) 自建 MCP Server 语言 Go Node.js / TypeScript 自选 工具数 542 ~30 自定义 资源数 46 0 自定义 功能覆盖 53 个模块全覆盖 基础 CRUD 按需 认证方式 Token / App / Account Account 自定义 维护状态 活跃 较少更新 自维护 编译方式 go build 生成单文件 npm install 自定义 选择 ZenTaoMcp 的理由：功能最全（542 工具覆盖 53 模块）、部署简单（Go 单文件）、认证灵活、活跃维护。\n技术原理：两种方案的底层机制 方案一：禅道 REST API + MCP 协议 禅道从 18.0 版本开始提供标准的 REST API，支持 JSON 格式的请求和响应。核心认证方式有三种：\n认证方式 说明 适用场景 实测状态 Token 用户名密码登录获取 Token 通用，最简单 ⚠️ 未验证 App 管理员创建应用，获取 APP_CODE + APP_KEY 服务端集成 ✅ 读取正常 / ❌ 写入静默失败 Account 直接使用账号密码 临时调试 ⚠️ 未验证 方案一采用 App 认证，因为：\n不暴露用户密码 可细粒度控制权限 Token 不会过期（APP_CODE + APP_KEY 长期有效） 适合 AI 助手持续调用 ⚠️ 实测发现：App 认证只能读取数据，创建/修改等写操作返回成功但数据未持久化。详见后文「实测诊断」章节。\nMCP（Model Context Protocol）是连接 AI 助手与外部工具的标准协议。方案一的工作流程：\nAI 助手 ↔ MCP Client ↔ MCP Server (stdio/SSE) ↔ 禅道 REST API WorkBuddy 内置了 MCP Client，只需配置 MCP Server 的启动命令，就能自动发现和调用所有工具。\n方案二：Web 表单接口 + Session 认证 方案二完全绕过了禅道的 REST API，转而模拟浏览器操作：\nAI 助手 → Python 脚本 (zentao.py) → 禅道 Web 表单接口 → Session Cookie 认证 核心原理：\n用账号密码模拟浏览器登录禅道，获取 zentaosid Session Cookie Session Cookie 关联了用户身份，与 App 认证的\u0026quot;应用身份\u0026quot;截然不同 直接调用禅道的 Web 表单接口（如 /task-create-{projectID}.json），与浏览器中手动操作完全一致 因为有用户身份，所有写操作（创建、编辑、关闭）都能正常执行 为什么 Web 表单接口比 REST API 更可靠？\n维度 REST API (/api.php/v1/...) Web 表单接口 (/task-create-*.json) 认证 App Token 或 Session Session Cookie 写入 App 认证下静默失败 Session 下正常工作 文档 官方文档 无公开文档，需抓包逆向 稳定性 跟随版本更新 与 Web UI 同步，较稳定 覆盖 仅 v1 版本的模块 与 Web 界面功能完全对等 代价：Web 表单接口没有公开文档，参数名需通过浏览器开发者工具抓包获得。且存在一些反直觉的行为（如 ZenTao 12.5.2 的 parent 参数在创建时被忽略），需要逐一实测验证。\n第一步：安装 Go 语言环境 ZenTaoMcp 是 Go 语言编写的，需要先安装 Go 来编译。\n1 2 3 4 5 6 7 8 9 10 # 从阿里云镜像下载（国内速度更快） # 访问 https://mirrors.aliyun.com/golang/ 选择对应版本 # 我用的是 go1.23.9.windows-amd64.zip # 解压到指定目录 Expand-Archive -Path go1.23.9.windows-amd64.zip -DestinationPath D:\\Software\\AI\\go\\ # 验证安装 D:\\Software\\AI\\go\\go\\bin\\go.exe version # 输出：go version go1.23.9 windows/amd64 💡 为什么不下载预编译版本？ ZenTaoMcp 的 GitHub Releases 没有提供 Windows 预编译版本，需要自行从源码编译。Go 的编译速度很快，整个编译过程只需 1-2 分钟。\n第二步：克隆并编译 ZenTaoMcp 1 2 3 4 5 6 7 8 9 10 11 cd D:\\Software\\AI # 克隆源码 git clone https://github.com/bivex/ZenTaoMcp.git cd ZenTaoMcp # 设置 Go 代理（国内加速） set GOPROXY=https://goproxy.cn,direct # 编译 D:\\Software\\AI\\go\\go\\bin\\go.exe build -o zentao-mcp.exe ./src/ 编译输出：\n生成文件：D:\\Software\\AI\\ZenTaoMcp\\zentao-mcp.exe 文件大小：约 16MB 依赖版本：mark3labs/mcp-go v0.44.0-beta.1 编译完成后，就得到一个独立的 zentao-mcp.exe，不需要 Go 运行时，也不需要 Node.js，拷贝到任何 Windows 机器都能用。\n第三步：验证 MCP Server 在配置到 WorkBuddy 之前，先验证 MCP Server 是否正常工作：\n1 2 3 4 5 6 7 8 9 10 11 # 直接运行，查看启动信息 D:\\Software\\AI\\ZenTaoMcp\\zentao-mcp.exe --help # 带环境变量运行（测试模式） set ZENTAO_BASE_URL=http://你的禅道地址/api.php set ZENTAO_APP_CODE=你的AppCode set ZENTAO_APP_KEY=你的AppKey set ZENTAO_AUTH_METHOD=app set ZENTAO_ALLOW_INSECURE_HTTP=true D:\\Software\\AI\\ZenTaoMcp\\zentao-mcp.exe MCP Server 正常启动后，会通过 stdin/stdout 与 MCP Client 通信，不会输出任何内容到控制台（这是正常的）。\n⚠️ 关键修正：ZENTAO_BASE_URL 必须包含 /api.php 后缀。如果只配置到根路径（如 http://禅道地址:88），MCP Server 构建的 URL 会缺少 /api.php，导致所有请求被重定向到登录页面。正确示例：http://你的禅道地址:88/api.php\n第四步：配置禅道 App 认证 在禅道管理后台创建应用，获取 APP_CODE 和 APP_KEY：\n登录禅道管理后台 进入 后台 → 二次开发 → 应用 点击 添加应用 填写应用名称（如 AI-Assistant） 设置权限范围（建议按需开放，不要全部授权） 保存后获得 APP_CODE 和 APP_KEY ⚠️ 安全提示：APP_KEY 是敏感信息，不要硬编码在代码中或提交到版本库。通过环境变量传递是更安全的做法。\n第五步：配置 WorkBuddy MCP 连接 在 WorkBuddy 的 MCP 配置文件中添加禅道服务器。\n配置文件位置 C:\\Users\\\u0026lt;你的用户名\u0026gt;\\.workbuddy\\mcp.json 💡 注意：配置文件名是 mcp.json（不是 .mcp.json），位于 ~/.workbuddy/ 目录下。\n配置内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 { \u0026#34;mcpServers\u0026#34;: { \u0026#34;zentao\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;D:\\\\Software\\\\AI\\\\ZenTaoMcp\\\\zentao-mcp.exe\u0026#34;, \u0026#34;env\u0026#34;: { \u0026#34;ZENTAO_BASE_URL\u0026#34;: \u0026#34;http://你的禅道地址:88/api.php\u0026#34;, \u0026#34;ZENTAO_APP_CODE\u0026#34;: \u0026#34;你的AppCode\u0026#34;, \u0026#34;ZENTAO_APP_KEY\u0026#34;: \u0026#34;你的AppKey\u0026#34;, \u0026#34;ZENTAO_AUTH_METHOD\u0026#34;: \u0026#34;app\u0026#34;, \u0026#34;ZENTAO_ALLOW_INSECURE_HTTP\u0026#34;: \u0026#34;true\u0026#34; } } } } 环境变量说明 变量 说明 示例 注意事项 ZENTAO_BASE_URL 禅道 API 地址 http://192.168.x.x:88/api.php 必须带 /api.php 后缀 ZENTAO_APP_CODE App 认证 Code MyApp — ZENTAO_APP_KEY App 认证 Key a1b2c3d4e5f6... 脱敏处理，勿提交版本库 ZENTAO_AUTH_METHOD 认证方式 app 仅 App 认证已实测 ZENTAO_ALLOW_INSECURE_HTTP 允许 HTTP（非 HTTPS） true 内网禅道通常为 HTTP ⚠️ 路径转义：Windows 路径中的反斜杠 \\ 在 JSON 中需要转义为 \\\\。建议用 Python json.dump() 写入配置文件，避免手动转义出错。\n第六步：信任 MCP 服务器 配置完成后，MCP Server 不会自动激活，需要手动信任：\n打开 WorkBuddy 客户端 进入连接器管理页面 在顶部自定义连接器入口找到 zentao 点击信任按钮激活 重启 WorkBuddy 会话使 MCP 生效 信任完成后，WorkBuddy 就能发现并调用禅道的 542 个工具了。\n第七步：创建禅道 Skill 为了让 AI 助手更好地理解禅道的功能和使用方式，我创建了一个 Skill 文件。\nSkill 定义 在 ~/.workbuddy/skills/zentao/SKILL.md 中定义，包含：\n架构说明：WorkBuddy → MCP → zentao-mcp.exe → 禅道 REST API 53 个功能模块的分类说明 常用场景示例：查看 Bug、项目进度、需求详情、创建任务等 环境变量：完整的配置说明 故障排查：连接超时、认证失败等常见问题 已知限制：App 认证只读、MCP Server Bug 等（重要） 支持的功能模块（53 个） 核心模块一览：\n类别 模块 说明 项目管理 products / projects / executions / programs 产品、项目、迭代、项目集 需求管理 stories / epics 需求/Story、史诗需求 任务管理 tasks 任务创建、分配、跟踪 Bug 管理 bugs Bug 列表、创建、解决、关闭 测试管理 testcases / testtasks / testsuite / testreport 用例、任务、套件、报告 发布管理 plans / releases / builds 发布计划、版本、构建 协作 todos / docs / feedbacks / tickets 待办、文档、反馈、工单 其他 users / search / kanban / ai / bi / admin 用户、搜索、看板、AI、看板、管理 使用方式 配置好 Skill 后，直接在 AI 助手对话中说：\n帮我查看禅道上指派给我的 Bug ✅ 已验证可用 帮我看看 XX 项目的进度 ✅ 已验证可用 帮我在禅道搜索 XX 关键词 ✅ 已验证可用 帮我在禅道创建一个 Bug：标题 XX ❌ App 认证下写入静默失败 AI 助手会自动调用对应的 MCP 工具，查询并整理结果。\n实测诊断：App 认证的真实能力边界 本章节基于 2026-06-08 晚间的完整实测，所有结论均经实际 API 调用验证。\n一、App 认证：读取正常，写入静默失败 这是最关键的发现。我尝试了多种方式在禅道中创建子任务，所有写操作均返回成功响应，但数据未实际持久化：\n测试方式 请求路径 响应 实际结果 MCP Server create_task 工具 POST /executions/{id}/tasks 空响应 200 ❌ 未创建（另有路径转换 Bug） App 认证 + 表单 POST api.php?m=task\u0026amp;f=create {\u0026quot;result\u0026quot;:\u0026quot;success\u0026quot;,\u0026quot;message\u0026quot;:\u0026quot;保存成功\u0026quot;} ❌ 未创建 App 认证 + JSON POST api.php?m=task\u0026amp;f=create 空响应 / Token 过期 ❌ 未创建 App 认证获取 Session 后提交 api.php?m=task\u0026amp;f=create 302 重定向到 /my/ ❌ 未创建 REST API v1 api.php/v1/tasks 缺少code参数 / Token已失效 ❌ 未创建 根因分析：App 认证生成的 Session 未关联任何用户账户。禅道的写操作需要用户身份上下文，App 认证只提供了\u0026quot;应用\u0026quot;身份，而非\u0026quot;用户\u0026quot;身份。因此：\n✅ 读取操作正常 — 查看任务、Bug、需求、项目等 ❌ 写入操作静默失败 — 返回成功但数据未持久化 ⚠️ \u0026ldquo;静默失败\u0026quot;是最危险的 — 如果不验证实际数据，很容易误以为操作成功。建议每次写操作后，立即用读操作确认数据是否真正存在。\n二、ZENTAO_BASE_URL 必须包含 /api.php 后缀 这是一个很容易忽略的配置细节。MCP Server 的 buildURL() 方法会将 ZENTAO_BASE_URL 与查询参数直接拼接：\nZENTAO_BASE_URL 配置 构建的 URL 结果 http://禅道地址:88 http://禅道地址:88?m=task\u0026amp;f=view\u0026amp;... ❌ 重定向到登录页 http://禅道地址:88/api.php http://禅道地址:88/api.php?m=task\u0026amp;f=view\u0026amp;... ✅ 正常返回数据 不带 /api.php 时，请求命中禅道 Web 根路径，被重定向到登录页面。带 /api.php 后，走的是禅道的 API 入口，App 认证正常生效。\n修正后的正确配置：\n1 \u0026#34;ZENTAO_BASE_URL\u0026#34;: \u0026#34;http://你的禅道地址:88/api.php\u0026#34; 三、MCP Server 路径转换 Bug ZenTaoMcp 的 convertRESTPath() 方法存在一个路径转换缺陷，影响任务创建等操作：\nBug 表现：\nREST 路径 期望转换 实际转换 结果 POST /executions/1692/tasks ?m=task\u0026amp;f=create\u0026amp;execution=1692 ?m=execution\u0026amp;f=create ❌ 错误的模块和函数 Bug 原因： 当 POST 请求的路径为 /executions/{id}/tasks 时，convertRESTPath() 将 executions 映射为 module execution，但没有识别 tasks 作为子资源（subResource）。正确的行为应该是将 tasks 映射为 module task，并将 execution 作为参数传递。\n影响范围： 所有涉及\u0026quot;父资源/ID/子资源\u0026quot;模式的创建操作可能受影响，不仅限于 tasks。\n状态： 已确认但未修复 — 需修改 src/client/client.go 中的 convertRESTPath() 逻辑并重新编译。\n四、create_task 工具缺少 parent 参数 即使修复了上述路径转换 Bug，create_task 工具仍无法创建子任务，因为工具定义中缺少 parent 参数：\n1 2 3 4 5 6 7 8 9 10 // 当前 create_task 工具定义（简化） createTaskTool := mcp.NewTool(\u0026#34;create_task\u0026#34;, mcp.WithNumber(\u0026#34;execution\u0026#34;, mcp.Required()), mcp.WithString(\u0026#34;name\u0026#34;, mcp.Required()), mcp.WithString(\u0026#34;type\u0026#34;, mcp.Required(), mcp.Enum(...)), mcp.WithArray(\u0026#34;assignedTo\u0026#34;, mcp.Required()), mcp.WithString(\u0026#34;estStarted\u0026#34;, mcp.Required()), mcp.WithString(\u0026#34;deadline\u0026#34;, mcp.Required()), // ❌ 缺少 parent 参数 — 无法指定父任务 ID ) 状态： 已确认但未修复 — 需在 src/tools/tasks.go 中添加 parent 参数并重新编译。\n五、网络代理干扰 如果本机运行了代理工具（如 Clash），Bash/curl 默认会走代理，导致内网禅道地址不可达：\n1 2 3 4 5 # ❌ 默认走代理，内网地址不可达 curl http://禅道地址:88/api.php?m=task\u0026amp;f=view\u0026amp;taskID=1 # ✅ 显式绕过代理 curl --noproxy \u0026#39;*\u0026#39; http://禅道地址:88/api.php?m=task\u0026amp;f=view\u0026amp;taskID=1 MCP Server 作为 WorkBuddy 子进程运行，不继承 Shell 的代理环境变量，因此不受此影响。\n六、首次信任 MCP 后 spawn ENAMETOOLONG 报错 在 WorkBuddy 中首次信任 zentao MCP Server 后，任何对话提问都会报如下错误：\nMCP error -32603: spawn ENAMETOOLONG 现象：\n信任 MCP 后立即使用 → 所有对话报错（不仅仅是调用禅道工具时） 关闭该 MCP 连接后重开 → 一切正常 后续重启会话也不再复现 原因分析：这是 Windows 命令行长度限制问题。WorkBuddy 在首次 spawn MCP Server 时，通过命令行参数传递 MCP 协议握手信息（包括工具列表、能力声明等）。ZenTaoMcp 注册了 542 个工具，序列化后的握手数据体量较大，导致命令行总长度超出 Windows 的 MAX_PATH / 命令行长度限制（约 32,767 字符），从而触发 ENAMETOOLONG。\n这不是 ZenTaoMcp 的 Bug — 而是 MCP 工具数量过多时，Windows 平台上 stdio 传输的命令行长度瓶颈。工具数量较少的 MCP Server 不会触发此问题。\n解决方法：\n方法 操作 推荐度 关闭重开 MCP 在连接器管理中关闭 zentao，再重新开启 ⭐⭐⭐ 首选 缩短 exe 路径 将 zentao-mcp.exe 移到更短路径（如 D:\\zt\\zentao-mcp.exe） ⭐⭐ 辅助 等待客户端修复 WorkBuddy 后续版本可能改用环境变量或临时文件传递握手数据 ⭐ 长期方案 💡 经验：首次信任任何工具数量较多的 MCP Server 后，如果出现全对话报错，先尝试关闭重开该 MCP 连接，大概率是此问题。\n实测结论汇总 能力 App 认证状态 备注 查看任务详情 ✅ 已验证 get_task 返回完整数据含子任务 查看 Bug 列表 ✅ 已验证 — 查看项目/迭代 ✅ 已验证 — 全局搜索 ✅ 已验证 — 查看统计数据 ✅ 已验证 — 创建任务/子任务 ❌ 静默失败 返回成功但数据未持久化 创建 Bug ❌ 静默失败 同上 修改/关闭 Bug ❌ 静默失败 同上 创建需求 ❌ 静默失败 同上 任何写操作 ❌ 静默失败 App 认证 Session 无用户身份 写入能力的解决方案 方案一（MCP + App 认证）的写入静默失败是根本性限制。以下方案从\u0026quot;已验证\u0026quot;到\u0026quot;未验证\u0026quot;排列：\n方案 原理 优点 缺点 验证状态 E. zentao-cli Web 登录 Python 脚本模拟浏览器登录，获取 Session 完整读写，无需管理员操作 需暴露账号密码；需维护 Python 脚本 ✅ 已验证可用 A. 提供用户凭据 MCP Server 切换为 Session/Account 认证 完整读写能力 需暴露账号密码；需修复 MCP Server Bug ⚠️ 部分验证 B. 管理员调整 App 权限 禅道后台为 App 授予写权限 不暴露用户凭据 不确定禅道是否支持此粒度 ⚠️ 未验证 C. 修复 MCP Server + Session 认证 修复路径转换 Bug，添加 parent 参数，配合 Session 认证 技术层面完整 仍需用户凭据；需 Go 开发能力 ⚠️ 待修复 D. 手动操作 AI 读取 + 人工写入 最安全，零风险 无法自动化 ✅ 可行 💡 建议路径：优先使用方案 E（zentao-cli），已经完整验证读写能力。如需 542 工具的全覆盖读取，可方案一（MCP）+ 方案二（CLI）并行。\n完整架构图 ┌─────────────────────────────────────────────────────────────┐ │ AI 助手 │ │ (WorkBuddy / 其他) │ └────────────┬──────────────────────────┬─────────────────────┘ │ │ 方案一：MCP 协议 方案二：Python CLI │ │ ▼ ▼ ┌────────────────────────┐ ┌─────────────────────────────────┐ │ zentao-mcp.exe (Go) │ │ zentao.py (Python 脚本) │ │ 542 tools / 53 模块 │ │ Web 登录 + Session Cookie │ │ App 认证（只读） │ │ 完整读写 │ └────────────┬───────────┘ └────────────┬────────────────────┘ │ │ │ REST API │ Web 表单接口 │ App Token │ Session Cookie ▼ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 禅道项目管理系统 │ │ (http://禅道地址:88) │ │ ┌────────┬────────┬────────┬────────┐ │ │ │ 产品 │ 项目 │ Bug │ 需求 │ │ │ ├────────┼────────┼────────┼────────┤ │ │ │ 任务 │ 用例 │ 发布 │ 文档 │ │ │ └────────┴────────┴────────┴────────┘ │ │ │ │ 方案一(MCP)：✅ 读取正常 / ❌ 写入静默失败 │ │ 方案二(CLI)：✅ 读取正常 / ✅ 写入正常 │ └─────────────────────────────────────────────────────────────┘ 三套系统对比：微信 / 企微 / 禅道 接入三个系统后，整体对比：\n对比项 个人微信 企业微信 禅道（MCP + CLI） 数据类型 非结构化聊天记录 非结构化聊天记录 + 组织架构 结构化项目数据 接入方式 本地数据库解密 本地数据库解密 REST API + Web 表单接口 加密难度 ⭐⭐⭐⭐ (SQLCipher 4) ⭐⭐ (wxSQLite3) ⭐ (无加密，API 鉴权) 工具链 wechat-cli + wx_key wechat-decrypt ZenTaoMcp + zentao-cli AI 可操作性 只读 只读 MCP 只读 / CLI 完整读写 数据时效性 需重新解密 需重新解密 实时（API 调用） 网络要求 本地 本地 需要网络连通 核心区别：微信和企微是\u0026quot;破解本地数据库\u0026rdquo;，禅道是\u0026quot;调用官方 API / 模拟浏览器操作\u0026rdquo;。禅道接入更规范、更稳定。MCP 方式功能全面但只读；CLI 方式通过 Web 登录获取完整读写能力，弥补了 MCP 的根本缺陷。两者并行使用是最佳实践。\n踩坑记录 # 问题 根本原因 解决方法 验证状态 1 GitHub Releases 没有 Windows 预编译版 作者只发布了 Linux/macOS 版本 自行从源码编译 go build ✅ 已验证 2 Go 下载慢（国内网络） Go 官方 CDN 在国内速度慢 使用阿里云镜像 + GOPROXY=https://goproxy.cn ✅ 已验证 3 MCP 配置 JSON 路径转义错误 Bash heredoc 会吞掉反斜杠 用 Python json.dump() 写入配置文件 ✅ 已验证 4 禅道 API 连接超时 禅道在内网，AI 沙箱环境不在同一网络 需在公司网络或 VPN 环境使用 ✅ 已验证 5 ZENTAO_ALLOW_INSECURE_HTTP 未设置 禅道使用 HTTP 而非 HTTPS 设置环境变量为 true ✅ 已验证 6 MCP Server 配置后未生效 需要手动信任并重启会话 在连接器管理中信任 zentao 服务器 ✅ 已验证 7 读取操作返回登录页 HTML ZENTAO_BASE_URL 缺少 /api.php 后缀 配置为 http://禅道地址:88/api.php ✅ 已验证 8 写操作返回\u0026quot;保存成功\u0026quot;但未生效 App 认证 Session 无用户身份，写操作静默失败 切换为 Session/Account 认证，或手动操作 ❌ 当前无法解决 9 MCP Server 创建任务发到错误 URL convertRESTPath() 对 /executions/{id}/tasks 转换错误 需修改 Go 源码 client.go 并重新编译 ⚠️ 待修复 10 无法创建子任务 create_task 工具缺少 parent 参数 需修改 Go 源码 tasks.go 并重新编译 ⚠️ 待修复 11 内网 curl 失败但浏览器正常 本机代理工具拦截内网请求 curl 加 --noproxy '*' ✅ 已验证 12 MCP 配置文件路径错误 Skill 文档中写的是 .mcp.json，实际应为 mcp.json 修正文档为 mcp.json ✅ 已验证 13 首次信任 MCP 后 spawn ENAMETOOLONG 542 个工具的握手数据超出 Windows 命令行长度限制 关闭重开 MCP 连接即可恢复 ✅ 已验证 14 zentao-cli Session 过期后操作失败 Session Cookie 有时效，过期后所有操作返回 401 重新执行 login 命令即可 ✅ 已验证 15 创建子任务必须两步法 ZenTao 12.5.2 的 parent 参数在创建时被忽略 先创建普通任务，再编辑设置 parent ✅ 已验证 16 assignedTo vs assignedTo[] 混淆 创建接口用 assignedTo[]（带括号），编辑接口用 assignedTo（无括号） 严格区分两个接口的字段名 ✅ 已验证 17 POST 返回空 body 缺少 CSRF token (kuid) 导致 PHP 级错误 从创建/编辑页面 HTML 中提取 uid 隐藏字段 ✅ 已验证 18 调试过程创建大量测试任务 每次试错直接调用 API，未先做 mock 测试 关闭测试任务清理；建议先在沙箱验证逻辑 ✅ 已验证 19 关闭任务接口返回非标准 JSON task-close 接口可能返回 302 重定向或空响应 不依赖 JSON 解析，检查 HTTP 状态码 ✅ 已验证 重点展开：问题 #8 —— App 认证写入静默失败 这是最隐蔽、最危险的问题。禅道的 App 认证 API 对写操作返回的响应与成功时完全一致：\n1 2 // App 认证创建任务 — 返回\u0026#34;成功\u0026#34;但数据未持久化 {\u0026#34;result\u0026#34;:\u0026#34;success\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;保存成功\u0026#34;} 如果不做二次验证（读取确认数据是否存在），很容易误以为操作成功。建议所有通过 AI 执行的写操作，都必须在操作后立即用读操作验证数据是否真正写入。\n底层原因：禅道 App 认证的工作机制是：\n用 APP_CODE + APP_KEY + 时间戳 生成 Token Token 用于 API 鉴权（相当于\u0026quot;应用身份\u0026quot;） 写操作需要\u0026quot;用户身份\u0026quot;上下文，App 认证只提供\u0026quot;应用身份\u0026quot; 禅道不返回权限错误，而是静默忽略写操作 可能的解决方向：\n切换为 Account 认证（直接传用户名密码） 切换为 Session 认证（先登录获取 Session ID） 在禅道后台为 App 开放写权限（需确认是否支持） 手动操作，AI 仅做读取辅助 重点展开：问题 #7 —— ZENTAO_BASE_URL 的 /api.php 后缀 这是一个配置级别的坑。MCP Server 的 URL 拼接逻辑为：\n最终URL = ZENTAO_BASE_URL + \u0026#34;?\u0026#34; + 查询参数 配置 拼接结果 效果 http://禅道地址:88 http://禅道地址:88?m=task\u0026amp;f=view ❌ 命中 Web 根路径，重定向到登录页 http://禅道地址:88/api.php http://禅道地址:88/api.php?m=task\u0026amp;f=view ✅ 命中 API 入口，App 认证生效 这不是 MCP Server 的 Bug — 禅道的 API 确实以 /api.php 为入口，只是文档中没有明确说明这个配置要求。\n注意事项与风险提示 ⚠️ 使用前请充分了解以下风险：\nApp 认证只读 — App 认证模式下，所有写操作（创建/修改/删除）静默失败。不要依赖 API 返回的\u0026quot;成功\u0026quot;消息，必须二次验证 ❌ 当前已确认 写入静默失败 — 这是最危险的：API 返回成功但数据未持久化，容易造成误判 数据安全 — 禅道数据包含项目进度、Bug 详情、需求文档等，属于企业内部信息 操作审计 — 通过 AI 执行的写操作（如果后续切换为 Session 认证）应可追溯 网络暴露 — 如果将禅道 API 暴露到公网，务必配置 HTTPS + IP 白名单 凭据安全 — APP_KEY 不要硬编码、不要提交到版本库，通过环境变量传递 版本兼容 — ZenTaoMcp 基于禅道 18.0+ 的 API，低版本禅道可能不完全兼容 MCP Server Bug — 路径转换缺陷和 parent 参数缺失是已知问题，需源码修复 建议：\n初期使用 App 认证的只读模式，确认数据读取稳定可靠 如需写入，优先确认禅道管理员能否为 App 开放写权限 定期检查禅道后台的应用调用日志 APP_KEY 定期轮换 发布博客或文章时，不要暴露真实的 API 地址、APP_CODE、APP_KEY 所有 AI 执行的写操作，必须做二次读取验证 总结 方案一：ZenTaoMcp MCP Server 环节 工具 难度 实测状态 备注 安装 Go 官方/镜像下载 ⭐ ✅ 国内用阿里云镜像加速 编译 ZenTaoMcp go build ⭐ ✅ 单文件输出，约 16MB 配置禅道 App 认证 禅道后台 ⭐ ✅ 记录 APP_CODE 和 APP_KEY 配置 WorkBuddy MCP mcp.json ⭐⭐ ✅ 注意 /api.php 后缀和路径转义 信任 MCP 服务器 WorkBuddy UI ⭐ ✅ 首次信任后关闭重开，避免 ENAMETOOLONG 创建 Skill SKILL.md ⭐⭐ ✅ 一次配置，长期使用 读取操作 App 认证 ⭐⭐ ✅ 正常工作 写入操作 App 认证 — ❌ 静默失败，需 Session 认证 创建子任务 MCP Server — ❌ 缺少 parent 参数 + 路径转换 Bug 网络连通 VPN / 内网 ⭐⭐⭐ ✅ 最常见的障碍，注意代理干扰 方案二：zentao-cli Web 登录 环节 工具 难度 实测状态 备注 部署脚本 zentao.py ⭐ ✅ Python 脚本，免编译 登录 account + password ⭐ ✅ Session Cookie 认证 读取操作 Web 接口 ⭐⭐ ✅ 项目/任务/Bug/迭代 写入操作 Web 表单接口 ⭐⭐ ✅ 创建/编辑/关闭任务 创建子任务 两步法 ⭐⭐⭐ ✅ ZenTao 12.5.2 Bug，需先创建再编辑 关闭任务 task-close 接口 ⭐⭐ ✅ 注意非标准 JSON 响应 Session 管理 自动 ⭐ ✅ 过期后重新 login 网络连通 requests 库 ⭐ ✅ 不受系统代理影响 最佳实践：方案一 + 方案二并行 读取 → 方案一（MCP，542 工具，覆盖广） 写入 → 方案二（CLI，Session 认证，完整读写） 核心收获：禅道接入 AI 的本质是\u0026quot;标准化 API 对接\u0026quot;，不像微信/企微需要破解本地数据库。方案一（MCP）提供了 542 个工具的单文件部署方案，一次编译覆盖禅道全部 53 个功能模块，但 App 认证只能读取，写入静默失败。方案二（zentao-cli）通过 Web 登录获取 Session，解决了写入问题，且部署更简单（Python 脚本免编译），但功能覆盖面不如 MCP 全面。两者并行是当前最优解——读取走 MCP 的广覆盖，写入走 CLI 的完整权限。\n方案二详解：zentao-cli Web 登录方式 以下内容基于 2026-06-09 晚间的完整实测，所有结论均经实际 API 调用验证。\n方案二的诞生背景 方案一（MCP + App 认证）的写入静默失败是根本性限制——App 认证的 Session 不关联任何用户账户，禅道的写操作需要用户身份上下文。与其等待 MCP Server 修复 + 切换认证方式，不如直接模拟浏览器的行为：用账号密码登录 → 获取 Session Cookie → 调用 Web 表单接口。\n这就是方案二的思路——绕过 REST API，走与浏览器完全一致的操作路径。\n安装与部署 方案二的核心是一个 Python 脚本 zentao.py，部署步骤极其简单：\n1. 确保 Python 环境可用\n1 2 3 # 验证 Python 3 已安装 python3 --version # 输出：Python 3.x.x 2. 安装依赖\n1 pip install requests 💡 zentao.py 仅依赖 requests，无其他第三方库。\n3. 部署脚本到 Skill 目录\n~/.workbuddy/skills/zentao-cli/ ├── SKILL.md # Skill 定义（AI 助手使用说明） └── zentao.py # Python 脚本（核心逻辑） 无需编译，无需 Go 环境，无需 MCP 配置。\n首次使用：登录 1 2 python3 ~/.workbuddy/skills/zentao-cli/zentao.py \\ --account 你的账号 --password 你的密码 login 登录成功后：\nSession Cookie 自动缓存到 ~/.workbuddy/zentao_cli_config.json 后续调用只需 --account 你的账号，脚本自动读取缓存 常用操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 查看个人信息 python3 zentao.py --account 你的账号 whoami # 查看我的任务 python3 zentao.py --account 你的账号 tasks # 查看我的 Bug python3 zentao.py --account 你的账号 bugs # 查看项目列表 python3 zentao.py --account 你的账号 projects # 搜索项目 python3 zentao.py --account 你的账号 projects -k 关键词 # 查看项目详情 python3 zentao.py --account 你的账号 view 项目ID 关键能力：创建子任务（两步法） 方案二最大的价值在于写入能力。但创建子任务有一个 ZenTao 12.5.2 的已知 Bug，需要分两步完成：\n为什么不能一步创建？\n在禅道 12.5.2 中，task-create 接口的 parent 参数会被静默忽略——即使 POST body 中包含 parent=38328，创建出的任务 parent 仍为 0（普通任务）。\n两步法操作：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import requests, json, re ZENTAO_URL = \u0026#39;http://你的禅道地址:88\u0026#39; session = requests.Session() # === 先登录获取 Session === resp = session.get(f\u0026#39;{ZENTAO_URL}/api-getSessionID.json\u0026#39;) inner = json.loads(resp.json().get(\u0026#39;data\u0026#39;, \u0026#39;{}\u0026#39;)) sid = inner.get(\u0026#39;sessionID\u0026#39;, \u0026#39;\u0026#39;) session.get(f\u0026#39;{ZENTAO_URL}/user-login.json?account=你的账号\u0026amp;password=你的密码\u0026amp;zentaosid={sid}\u0026#39;) # === Step 1: 创建普通任务（不带 parent）=== # 从创建页面获取 CSRF token (kuid) resp = session.get(f\u0026#39;{ZENTAO_URL}/task-create-{projectID}.html\u0026#39;) kuid = re.search(r\u0026#39;name=\u0026#34;uid\u0026#34;\\s+value=\u0026#34;([^\u0026#34;]+)\u0026#34;\u0026#39;, resp.text).group(1) r = session.post(f\u0026#39;{ZENTAO_URL}/task-create-{projectID}.json?onlybody=yes\u0026#39;, data={ \u0026#39;uid\u0026#39;: kuid, \u0026#39;project\u0026#39;: str(projectID), \u0026#39;type\u0026#39;: \u0026#39;devel\u0026#39;, \u0026#39;name\u0026#39;: \u0026#39;任务名称\u0026#39;, \u0026#39;desc\u0026#39;: \u0026#39;任务描述\u0026#39;, \u0026#39;assignedTo[]\u0026#39;: \u0026#39;用户名\u0026#39;, # 注意：创建用 assignedTo[]（带括号） \u0026#39;estStarted\u0026#39;: \u0026#39;2026-06-09\u0026#39;, \u0026#39;deadline\u0026#39;: \u0026#39;2026-06-16\u0026#39;, \u0026#39;estimate\u0026#39;: \u0026#39;4\u0026#39;, \u0026#39;pri\u0026#39;: \u0026#39;3\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;wait\u0026#39;, \u0026#39;module\u0026#39;: \u0026#39;0\u0026#39;, }) # 创建成功后，从项目任务列表中找到新任务的 ID # === Step 2: 编辑设置 parent === # 从编辑页面获取新的 kuid resp = session.get(f\u0026#39;{ZENTAO_URL}/task-edit-{newTaskID}.html\u0026#39;) kuid = re.search(r\u0026#39;name=\u0026#34;uid\u0026#34;\\s+value=\u0026#34;([^\u0026#34;]+)\u0026#34;\u0026#39;, resp.text).group(1) r = session.post(f\u0026#39;{ZENTAO_URL}/task-edit-{newTaskID}.json?onlybody=yes\u0026#39;, data={ \u0026#39;uid\u0026#39;: kuid, \u0026#39;parent\u0026#39;: str(parentTaskID), # 关键：设置父任务 \u0026#39;name\u0026#39;: \u0026#39;任务名称\u0026#39;, \u0026#39;type\u0026#39;: \u0026#39;devel\u0026#39;, \u0026#39;desc\u0026#39;: \u0026#39;任务描述\u0026#39;, \u0026#39;assignedTo\u0026#39;: \u0026#39;用户名\u0026#39;, # 注意：编辑用 assignedTo（无括号） \u0026#39;estStarted\u0026#39;: \u0026#39;2026-06-09\u0026#39;, \u0026#39;deadline\u0026#39;: \u0026#39;2026-06-16\u0026#39;, \u0026#39;estimate\u0026#39;: \u0026#39;4\u0026#39;, \u0026#39;pri\u0026#39;: \u0026#39;3\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;wait\u0026#39;, \u0026#39;module\u0026#39;: \u0026#39;0\u0026#39;, \u0026#39;comment\u0026#39;: \u0026#39;\u0026#39;, }) 实测验证：以上两步法已成功在 task-38328 下创建子任务 #39109 \u0026ldquo;TestZentao\u0026rdquo;，指派给 llizhong。\n关键注意事项 陷阱 说明 影响 parent 在创建时被忽略 task-create 接口的 parent 参数静默丢弃 必须用两步法 assignedTo vs assignedTo[] 创建接口用 assignedTo[]，编辑接口用 assignedTo 字段名不一致，混用会失败 CSRF token (kuid) 必须从创建/编辑页面 HTML 中提取 uid 隐藏字段 缺少则 PHP 报错，返回空 body Session 过期 Cookie 有时效，过期后所有操作返回 401 重新 login 即可 关闭任务非标准响应 task-close 接口可能返回 302 重定向 不依赖 JSON 解析，检查状态码 已验证能力汇总（2026-06-09） 能力 状态 备注 Web 登录 ✅ Session Cookie 认证，role: sw 项目列表 ✅ 1599 个项目可读 我的任务 ✅ — 我的 Bug ✅ — 创建普通任务 ✅ 一步完成 创建子任务 ✅ 两步法（ZenTao 12.5.2 Bug） 编辑任务 ✅ 修改名称、指派人、父任务等 关闭任务 ✅ task-close 接口，注意非标准响应 搜索项目 ✅ 关键词搜索 查看项目详情 ✅ — 方案二与方案一的详细对比 维度 方案一：ZenTaoMcp (MCP) 方案二：zentao-cli (Web 登录) 部署 需 Go 环境 + 编译 + MCP 配置 + 信任 Python 脚本，pip install requests 即可 认证 APP_CODE + APP_KEY（管理员创建） 账号密码（个人即可） 读取 ✅ 542 工具，53 模块全覆盖 ✅ 常用模块（项目/任务/Bug/迭代） 写入 ❌ App 认证静默失败 ✅ 完整读写（Session 有用户身份） 创建子任务 ❌ 缺 parent 参数 + 路径 Bug ✅ 两步法可用 数据隔离 多人共用一个 App 每人用自己的账号，天然隔离 凭据管理 APP_KEY 长期不变，集中管理 密码仅首次登录，后续用 Session 功能覆盖 ⭐⭐⭐⭐⭐（542 工具） ⭐⭐⭐（常用功能，需手写扩展） 稳定性 ⭐⭐⭐（MCP 进程，自动重连） ⭐⭐（Session 过期需重新登录） 代理兼容 MCP 不继承 Shell 代理 requests 不走系统代理，内网直通 调试难度 ⭐⭐⭐（需看 MCP 日志 + Go 源码） ⭐⭐（Python 脚本，可逐步调试） 适用场景 只读、多人共享、全模块覆盖 写入、个人使用、快速部署 推荐策略：方案一 + 方案二并行。日常查询走方案一（MCP 覆盖广、稳定），需要写入时走方案二（CLI 完整读写）。两者互补，不冲突。\n系列文章（AI 工具链） 本博客「AI 工具链」系列，教你把各类研发工具接入 AI 助手（悟空）：\n让 AI 管理你的代码仓库：Gitea MCP Server 接入指南 让 AI 帮你管代码评审：Gerrit 接入指南 让 AI 管理你的项目：禅道接入指南 让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南 让 AI 读懂你的企业微信：wechat-decrypt 接入指南 在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战 在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程 ","permalink":"https://sikinzen.github.io/posts/howtoconnectzentaoandai/","summary":"记录将禅道项目管理系统接入 AI 助手的两种方案：方案一使用 ZenTaoMcp MCP Server（App 认证，读取正常但写入静默失败）；方案二使用 zentao-cli Web 登录（Session 认证，完整读写能力）。含完整踩坑记录与优劣对比","title":"让 AI 管理你的项目：禅道接入 AI 的两种方案"},{"content":"前言 对于技术从业者而言，访问 GitHub、查阅英文文档、使用海外 API 等场景是日常刚需。然而由于网络环境的限制，这些操作时常受阻。本文记录个人使用的一套科学上网方案，涵盖服务选购、PC 端与移动端配置全流程，供有同样需求的朋友参考。\n声明：本文仅作技术交流与个人经验记录，请读者遵守所在地法律法规，合理使用网络工具。\n一、服务选购 市面上的代理服务（俗称\u0026quot;机场\u0026quot;）繁多，选择时需关注以下维度：\n线路质量：延迟、带宽、稳定性 流量配额：月流量是否满足日常使用 客户端兼容性：是否支持 Clash、Shadowrocket 等主流客户端 价格合理性：性价比是否匹配个人需求 推荐服务 个人目前在用 AgentNEO，体验稳定。注册流程如下：\n进入官网，免费注册账号 登录后系统会引导选择套餐，按需选购即可 以我选择的套餐为例：\n项目 说明 流量 150 GB/月 月付 ¥50/月 年付 ¥400/年（约 ¥33/月） 购买完成后，即可在 Dashboard 中管理订阅与查看节点状态。\n二、PC 端配置（Windows） 2.1 获取配置教程 登录 AgentNEO 后，在账号页面左下角找到 \u0026ldquo;配置教程\u0026rdquo;，点击后选择 Windows，进入官方文档页面：\n配置文档：https://docs.neobook.co/windows/clash-for-windows-pei-zhi-jiao-cheng\n后续操作基本按照该文档逐步执行即可。\n2.2 安装 Clash for Windows 配置教程的第一步是下载并安装 Clash for Windows 客户端：\n下载地址：https://github.com/Fndroid/clash_for_windows_pkg/releases 推荐版本：Clash.for.Windows-0.20.22-win.7z 下载后解压，运行 Clash for Windows.exe 即可启动。\n2.3 导入订阅 在 AgentNEO Dashboard 中复制 Clash 订阅链接 打开 Clash for Windows，进入 Profiles 页面 粘贴订阅链接，点击 Download 下载成功后，切换到 Proxies 页面，选择合适的节点 将系统代理开关打开（System Proxy），浏览器即可通过代理访问 关键步骤示意图（按文档操作即可）：\n1 Dashboard 复制订阅 → Clash Profiles 粘贴下载 → Proxies 选择节点 → 开启 System Proxy 三、Android 端配置 3.1 获取配置教程 与 PC 端类似，登录 AgentNEO 后在左下角找到 \u0026ldquo;配置教程\u0026rdquo;，选择 Android，进入：\n配置文档：https://docs.neobook.co/android/clash-for-android-pei-zhi-jiao-cheng\n3.2 安装 Clash for Android 按文档指引下载并安装 Clash for Android（APK 格式），安装完成后导入订阅链接，流程与 PC 端一致。\n3.3 关键注意事项：FINAL 规则修正 按照官方文档完成基础配置后，部分用户可能会遇到仍无法正常访问的问题。根本原因在于代理规则中的 FINAL 策略默认指向 PROXY，导致部分本该直连的流量被错误代理。\n解决方法：\n打开 Clash for Android 主界面 进入 代理（Proxy） 页面 找到 FINAL 规则 将其从 PROXY 修改为 DIRECT 1 Clash 主界面 → 代理 → FINAL → PROXY 改为 DIRECT 此操作确保未命中特定代理规则的流量走直连通道，避免不必要的代理导致的访问异常。\n四、常见问题排查 问题 可能原因 解决方案 订阅下载失败 网络波动或链接过期 重新从 Dashboard 复制订阅链接重试 节点全部超时 节点被屏蔽或本地 DNS 污染 切换节点或更新订阅获取最新节点列表 PC 端浏览器无法访问 System Proxy 未开启 检查 Clash 主界面的系统代理开关 Android 端部分 App 无法联网 FINAL 规则配置错误 将 FINAL 策略改为 DIRECT 速度不达预期 所选节点负载过高 切换到低延迟节点或联系服务商 五、总结 一套稳定可靠的科学上网方案由三个关键要素构成：\n可靠的服务商：线路质量决定体验下限 正确的客户端配置：订阅导入与规则设置是核心 及时的故障排查：了解常见问题能大幅减少 downtime 本文记录的个人方案基于 AgentNEO + Clash 组合，经过一段时间的实际使用验证，稳定性与速度均能满足日常开发需求。如果你有更好的方案或补充，欢迎交流讨论。\n本文首发于 温陵布衣，转载请注明出处。\n","permalink":"https://sikinzen.github.io/posts/howtousevpn/","summary":"一份面向技术从业者的实用指南，记录从代理服务选购到多端客户端部署的完整流程，帮助你高效访问海外技术资源。","title":"科学上网配置指南：从服务选购到多端部署"},{"content":" 原文参考：WorkBuddy 10 个上手技巧（WorkBuddy 官方文档）\n本文基于 WorkBuddy 官方文档整理，补充实战案例与个人使用体会。\n如果你是第一次使用 WorkBuddy，先掌握这 10 个高频技巧，就能更快学会如何把模糊想法变成可执行任务，让 AI 真正帮你交付结果。\n你可以把这篇指南理解为\u0026quot;新手上手避坑清单\u0026quot;：既讲怎么提需求，也讲怎么控制风险、怎么提高结果质量，以及什么时候适合交给 AI 自动执行。\n技巧 1：清晰表达，给 WorkBuddy 下达明确任务 做什么 + 有什么 + 怎么样，别让 AI 猜你的意图。\n很多新手第一次用 WorkBuddy，往往随手甩一句模糊需求过去，然后对结果大失所望。原因很简单：WorkBuddy 不会读心术。你给的信息越少，它能发挥的空间就越混沌。\n想让 WorkBuddy 精准执行，记住这个\u0026quot;三要素公式\u0026quot;：\n要素 含义 示例 做什么 任务目标 整理会议纪要 有什么 输入素材 D:/会议纪要/0320产品评审.docx 怎么样 输出要求 表格输出，含结论+责任人+截止日期 反面示范：\n帮我把上次的会议纪要整理一下。\n正面示范：\n把 D:/会议纪要/0320产品评审.docx 里的会议纪要整理成一份清单，包含：1）每个议题的结论；2）对应的责任人和截止日期；3）标记有争议、未决的事项。用表格输出，不需要开场白。\n💡 新手小贴士：如果不确定从哪里开始，可以先问 WorkBuddy：\u0026ldquo;我想做 XX，你需要我提供哪些信息？\u0026ldquo;让它先帮你梳理输入项，再进入正式执行。\n技巧 2：小步快跑，一次搞定一件事 把大任务拆成小步骤，稳稳地推进。\n新手最常犯的错误之一，就是把庞大的需求一口气丢给 WorkBuddy，比如\u0026quot;帮我把整个项目重构一遍\u0026quot;或\u0026quot;把这份 50 页的报告全部翻译并总结\u0026rdquo;。结果往往是：WorkBuddy 输出了很多内容，但没有一处真正让你满意。\n更好的做法是：把大任务拆解成独立的小目标，一次只推进一步。\n反面示范：\n帮我把这份 50 页的行业报告读一遍，写一份内部汇报 PPT 发给领导的邮箱。\n正面示范（拆成三轮）：\n第 1 轮：读取 D:/报告/2026年AI行业报告.pdf，提炼 5 个最核心的结论，每个结论用一句话概括，并附带原文页码。 第 2 轮：基于这 5 条结论，帮我写一份 15 页的内部汇报大纲，概述行业趋势，聚焦与我们业务相关的机会，并补充建议和下一步行动。 第 3 轮：大纲没问题，帮我把第二页\u0026quot;机会分析\u0026quot;扩展成完整的 PPT 页面内容，每条机会配一段 50 字以内的说明，并发给领导。 分步的好处是：每一步你都能先确认方向对不对，发现偏了就及时拉回来，而不是最后整份结果都要推翻重做。\n技巧 3：不满意就调整，别一轮定生死 对话是多轮的，第一个回答只是起点。\n很多用户第一次拿到不满意的结果，就直接放弃了——这其实是最可惜的。AI 对话的精髓在于不断打磨：第一轮先给方向，后续轮次再逐步收窄、校准。\n常见的调整方式包括：\n直接指出问题：例如\u0026quot;太长\u0026quot;\u0026ldquo;语气太正式\u0026quot;\u0026ldquo;第三段逻辑不通，重写\u0026rdquo; 换个角度重述需求：把原本抽象的要求换成更具体的业务场景 补充限制条件：加入受众、场景、格式、篇幅等要求 切换角色视角：让 WorkBuddy 站在老板、客户、产品经理、分析师等身份思考 示例：\n第 1 轮：帮我写一封给客户的道歉邮件，原因是上期交付的报表数据有误。 WorkBuddy 的回复：给出了一封正式、偏长的道歉信，语气过于严肃。 第 2 轮：太正式了，客户是合作了三年的老朋友，语气轻松一点，像微信消息的风格，200 字以内，重点说明问题原因和已修复，不用反复道歉。 WorkBuddy 的回复：语气更简洁，也更贴近日常沟通。 第 3 轮：不错，但补一句\u0026quot;下次交付前我会先发你确认版本\u0026rdquo;，放在结尾。 你可以把和 WorkBuddy 的对话想象成在指导一个很勤奋、但需要反馈的实习生——你越愿意说清楚\u0026quot;哪里不对、想要什么\u0026rdquo;，它产出的结果就越贴近你的心意。\n技巧 4：先本地后远程，稳扎稳打不踩坑 从桌面端开始，别一上来就远程遥控。\nWorkBuddy 支持通过微信、QQ 等软件远程发指令，配置相对简单。但远程遥控意味着 WorkBuddy 会在你看不见的地方自主行动。一旦它执行了超出预期的操作，你未必来得及叫停。\n建议的进阶路径：\n第 1 步（本地桌面端）：帮我列出 D:/下载 文件夹里的所有文件，按类型分组显示。 第 2 步（本地桌面端）：把其中的 .pdf 文件移到 D:/文档/PDF归档，.xlsx 文件移到 D:/文档/表格归档，其他不动。执行前先列出待移动清单让我确认。 第 3 步（确认判断无误后，再考虑远程）：以后每周五下午帮我跑一次同样的归档任务。 技巧 5：给 WorkBuddy 一个身份，它就更专业 \u0026ldquo;专家\u0026quot;功能，是最简单的提质手段之一。\nWorkBuddy 内置了\u0026quot;专家\u0026quot;能力，提供了各行各业的预设角色，例如法律顾问、产品经理、数据分析师、营销文案等。选择合适的专家后，WorkBuddy 会优先沿着该角色的思路来组织回答。\n本质上，这相当于给 WorkBuddy 预先设置了一层\u0026quot;角色提示\u0026rdquo;，可以让它的表达框架、关注重点和专业术语更快对齐对应领域。对于专业性较强、输出风格要求明确的任务，效果通常会更稳定。\n技巧 6：给 WorkBuddy 看例子，比说一堆要求更管用 一个好的参考样本，胜过写十行抽象要求。\n语言描述有时很难精确表达你想要的\u0026quot;感觉\u0026quot;。这时候，最省力也最有效的方式，往往就是直接把一个你满意的样本给 WorkBuddy，并告诉它：\u0026ldquo;照这个来。\u0026rdquo;\n相比从零猜测，你给出的样本就是一个非常明确的锚点。无论是语气、结构、格式，还是表达风格，只要参照物足够清晰，WorkBuddy 通常都能发挥得更稳定。\n技巧 7：善用多任务，别在烂上下文里死磕 不同的任务，开不同的会话。\n你可以把每个会话理解为一张独立的工作台：写方案一个窗口、整理数据一个窗口、回邮件一个窗口，彼此互不干扰，同时推进，效率会高很多。\n另一方面，如果一个会话过长，WorkBuddy 开始出现\u0026quot;前说后忘\u0026quot;\u0026ldquo;越聊越跑偏\u0026quot;的情况，就不要硬撑。直接开一个新任务，把关键背景重新简洁说一遍，往往比在旧上下文里反复修补更有效。\n技巧 8：记得频繁备份和做版本管理 WorkBuddy 可能\u0026quot;瞎改\u0026rdquo;，改崩了就得有路可退。\nWorkBuddy 再能干，也不是每次都绝对稳定。让它帮你修改文档、方案、表格之前，最好先把原文件复制一份备份好——哪怕只是在文件名后面加上\u0026quot;_备份\u0026quot;或当天日期。\nWorkBuddy 有时会改过头：删掉你想保留的内容、打乱原有格式，或者把方向越改越偏。等你发现不对劲时，原始版本可能已经被覆盖掉了。\n真正成熟的使用方式，不是期待它永远不出错，而是提前准备好回退方案。这样就算出错，也不会影响你的主版本成果。\n技巧 9：用好自动化，让 WorkBuddy 自己持续干活 善用定时任务和自动化工作流，让 WorkBuddy 24 小时运转。\nWorkBuddy 支持自动化能力，你不必每次都手动触发。通过定时任务和自动化工作流，它可以在后台静默处理很多重复性的日常工作，例如：\n每天 9:00 自动生成工作日报 代码提交后自动跑测试 每周汇总数据并生成报告 定期抓取并整理行业资讯 示例：每日早报\n设置一个每天早上 8:30 的自动化任务：发送一份每日新闻资讯早报到我的邮箱。\n示例：周报自动生成\n每周五 17:00 自动运行：汇总本周 D:/项目/ 下所有代码提交记录，按模块分类，统计每个模块的提交次数和主要改动内容，并生成一份简洁周报。\n哪些工作适合自动化？ 重复性高、规则明确、无需实时人工干预的任务，最适合交给 WorkBuddy 自动化处理。\n技巧 10：学会\u0026quot;偷懒\u0026quot; + 合理激发 WorkBuddy 的输出 善用参考、角色和激励，把重复劳动统统外包。\n这里说的\u0026quot;偷懒\u0026quot;不是摆烂，而是把机械性的重复工作尽可能交给 AI。格式化代码、写模板、整理数据这些标准化动作，本来就很适合让 WorkBuddy 处理。\n而所谓\u0026quot;激励式表达\u0026quot;，本质上是给 AI 更强的角色设定和质量预期。虽然 WorkBuddy 没有情绪，但这种表达会影响它的措辞选择、分析深度和输出结构。\n偷懒版（把机械劳动外包）：\n帮我把这个 Excel 里 2000 行数据中的电话号码统一成 xxx-xxxx-xxxx 的格式；区号如果是 0 开头就保留，否则统一补上默认区号 0755。\n激励版（用角色和质量标准提升结果）：\n你是一位在投行工作 10 年的财务分析师，以你见过最优秀的研究报告为标准，帮我分析这家公司的财报。重点关注：1）收入结构是否健康；2）现金流是否存在隐患；3）相比同行的竞争优势。请给出明确的投资建议；如果数据不足，可以大胆假设，但要标注假设条件。我要的是有洞察力的分析，而不是数据搬运。\n底层逻辑是：你的精力应该优先花在决策和创造性思考上，而机械性的执行工作，尽量交给 AI 代劳。这不是偷懒，而是让对的人和对的工具，去做更合适的事。\n推荐上手顺序 如果你刚开始使用 WorkBuddy，建议按下面的顺序逐步建立习惯：\n先学会清晰提需求：把任务说清楚，比什么技巧都重要 再学会分步推进和多轮调整：不要指望第一轮就完美 随后熟悉专家、样例和多任务能力：逐步提升结果质量 涉及真实文件时先备份，再尝试远程和自动化：先稳，再快 先把这 10 个技巧跑顺，后面无论是写文档、做分析、处理表格，还是搭建自动化流程，你都会明显更顺手。\n参考来源：WorkBuddy 官方文档 - 10 个上手技巧\n","permalink":"https://sikinzen.github.io/posts/howtouseworkbuddy2nd/","summary":"WorkBuddy 官方 10 个上手技巧，从清晰表达到自动化工作流","title":"WorkBuddy 从入门到精通：10 个上手技巧让你的 AI 真正帮你干活"},{"content":" 原文作者：智能进化Wayen（原创），来源：微信公众号原文链接\nWayen，世界500强企业教练+AI职场提效专家。专注研究AI提效、人才赋能、管理提升，信奉\u0026quot;把重复的事交给AI，把思考的事留给自己\u0026quot;。\n有没有很无聊，上个月我做了一个实验。\n我把一个竞品分析任务同时交给了两个人：一个新来的实习生，和 WorkBuddy。\n实习生的路径，是问我要背景资料、理解需求、出框架、确认范围、再执行、交付。\nWorkBuddy 的路径，是直接给了我一份文档。\n你猜谁的结果更好？\n实习生那版，方向是对的，但深度不够。WorkBuddy 那版，看起来很全，但有一半数据是错的。\n这件事让我想明白一件事：WorkBuddy 用得好不好，关键不在工具本身，在于你怎么用它。\n如果你把它当百度用——问一句答一句——那它就是个升级版聊天框。但如果你把它当一个需要管理的协作者，它就能帮你干很多事。\n小半年用下来，我总结了 6 条经验，每一条都是自己踩出来的。\n第一条：别急着让它干活，先让它认识你 很多人装完 WorkBuddy，第一件事就是丢任务过去。\n\u0026ldquo;帮我写一份本周周报。\u0026rdquo; \u0026ldquo;帮我整理这个文件夹。\u0026rdquo; \u0026ldquo;帮我搜一下最近 AI 圈有什么新闻。\u0026rdquo;\n然后发现结果总是不对味。\n原因是：WorkBuddy 根本不认识你。\n它不知道你是做什么的、你的工作习惯是什么、你用什么语气写东西、你对交付质量的要求有多高。\n我之前也是这么干的。直到有一回，我让它帮我写一份客户拜访纪要，它生成的版本用了大量的\u0026quot;尊敬的客户\u0026quot;和官方套话。我改的时间比重写还长。\n后来我做了三件事：\n第一，写了 .workbuddy/SOUL.md。里面不是写\u0026quot;你是一个高效的AI助手\u0026quot;这种废话，而是具体的：\n- 先给结论，有需要再展开 - 不确定的地方直接说\u0026#34;不确定\u0026#34;，不要编 - 数字必须核实来源 - 交付物按我给的模板走，不要自创格式 第二，写了 .workbuddy/USER.md。告诉它我是谁、做什么的、常用哪些工具、关注哪些领域。\n第三，写了 .workbuddy/IDENTITY.md。规定了它的操作边界：什么场景下可以自主执行，什么场景必须先问我。\n这三份文件加起来可能不到 500 字，但效果非常明显。同一个任务，配置前后的交付质量差了至少两档。\n这不是什么高级技巧，但大多数人就是不做。\n第二条：Skill 不是下载越多越好，而是把重复工作打包成自己的 WorkBuddy 有个 Skill 市场，里面得有成百上千个 Skill。\n我一开始的心态是：看到好的就装。装了几十个后，WorkBuddy 的反应明显变慢了，而且经常出现上下文冲突——它要在一堆 Skill 里找哪个相关，找着找着就偏了。\n后来我把没用的全删了，只留了18 个真正高频用的。\n但真正让我效率翻倍的，不是从市场下载，而是站在他人的肩膀上自己造 Skill。\n举个例子。\n我每周要出一份 AI 行业周报。以前的做法是：打开 WorkBuddy → 搜索相关新闻 → 整理成文 → 排版 → 保存。每次至少 30 分钟。\n后来我花了一个下午，捣鼓了一个叫 ai-news-radar 的 Skill。造的过程不复杂，就是把我的工作流写进去：\n1. 抓取过去一周指定来源的 AI 资讯 2. 按\u0026#34;产品发布/行业动态/论文突破/政策变化\u0026#34;分类 3. 每个类别只保留最重要的 3 条 4. 输出 800-1000 字的摘要简报 从那以后，每周周报变成了一个命令的事。30 分钟缩到了 3 分钟。\n而且这东西还能迭代。用了两周我觉得分类方式不够好，改了一版。又用了一个月，加上了\u0026quot;AI 数据分析和可视化\u0026quot;相关的新闻筛选项。\n造 Skill 这件事，核心不是写代码，而是 把你自己都嫌麻烦的固定流程，一次性打包好。\n第三条：复杂任务先让它出方案，不要直接让它干 这是我从 Codex 官网文章里学到的，拿来用之后，WorkBuddy 的交付质量直接翻倍。\n以前我的习惯是：\u0026ldquo;帮我做一个竞品分析，对象是 XXXX，重点关注他们的产品功能和定价策略。\u0026rdquo;\n然后 WorkBuddy 就开干了。出来的东西经常不是我想要的——有些维度它没覆盖，有些维度我又不需要。\n后来我改了第一步：\n\u0026ldquo;先不要执行。帮我设计一个竞品分析的框架，包括维度、数据来源、评估标准。我确认了再开始。\u0026rdquo;\n这样可以先看看它的理解跟我的需求一不一致。\n上周我做了一个真实的案例。我要对比6个AI编程工具。\n我先让它出框架，它给了一个 8 个维度的分析方案。我看了之后，删掉了两个我不关心的维度（\u0026ldquo;社区活跃度\u0026quot;和\u0026quot;文档完整性\u0026rdquo;），加了一个我特别在意的维度（\u0026ldquo;在飞书文档场景下的表现\u0026rdquo;）。\n确认之后，它再执行。那份表，我基本没改就直接用了。\n让 WorkBuddy 先出方案，本质是把你的角色从\u0026quot;等待交付\u0026quot;变成\u0026quot;审核方案\u0026quot;。 后者才是人该干的事。\n第四条：研究型任务，先让它搜，再让它说 WorkBuddy 有个能力很多人都知道但好像不会用——它能联网。\n但默认情况下，它更倾向于用训练数据里的知识回答你。训练数据是有截止日期的。\n我踩过这个坑。\n四月份 DeepSeek 新版本发布，我问 WorkBuddy \u0026ldquo;DeepSeek V4 有什么新特性\u0026rdquo;。它洋洋洒洒给我列了一堆，看起来很有道理。但我越看越不对劲，自己去搜了一下，发现它说的有一半是 V3 的功能，V4 真正更新的东西它根本没覆盖。\n后来我加了一句话：\n\u0026ldquo;先不要回答。请帮我搜索 DeepSeek V4 最新的发布信息和测评文章，整理后再给我结论。\u0026rdquo;\n结果完全不一样。\n现在我做研究型任务，都会加上这个前置指令。不管是查产品动态、行业数据还是竞品信息，先搜一遍再回答，和直接凭记忆回答，完全是两个质量级别。\n具体的话术我一般是：\n\u0026ldquo;帮我搜一下过去两周 XXXXX 方面的最新信息，列出找到的源，我确认后你再整理。\u0026rdquo;\n不只是结论更准，而且你还能看到它的信息来源，可信度你自己能判断。\n第五条：一个任务做完，开新会话 这个习惯是我被坑了好几次之后才养成的。\n有一次我做 WorkBuddy 的专家模式文档。做完了，写了大概 4000 字。接着我让它做一个新的任务——整理一篇关于保险代理人 AI 应用的读者投稿。\n它生成的初稿里，包含了\u0026quot;专家模式\u0026quot;\u0026ldquo;Skill 编排\u0026quot;\u0026ldquo;记忆系统\u0026quot;这些词。我看了半天，发现它把上一个任务的知识带到了新任务里，以为新文章也是 WorkBuddy 的系列内容。\n问题出在长会话。\nWorkBuddy 会在同一个上下文里积累之前的判断和假设。做完一件事再做另一件，它会\u0026quot;带着旧眼镜看新问题\u0026rdquo;。\n现在我的习惯是：\n做完一个任务后跟它说：\u0026ldquo;请用一段话总结刚完成的任务、修改过的文件、当前状态和下一步注意事项。\u0026rdquo;\n然后把这段总结复制出来。接着开新会话，把总结贴进去，再开始新任务。\n这个动作只需要 30 秒，但能省掉后面半小时的纠错时间。\n第六条：每次要求它输出验收清单 这是第六条，也是特别实用的一个习惯。\nWorkBuddy 有一个特点：它很少主动告诉你\u0026quot;这个我不确定\u0026quot;或\u0026quot;这部分我没验证过\u0026rdquo;。当你问它\u0026quot;完成了吗\u0026quot;，它通常会说\u0026quot;完成了\u0026quot;。\n不是它在骗你，而是它不知道自己不知道。\n我现在的做法是：每次交付前，要求它输出验收清单。\n比如我让它用 Python 写一个批处理脚本，再加一句：\n\u0026ldquo;完成后请输出：\n修改了哪些文件 每个文件什么作用 跑了什么验证 哪些地方还没验证 哪些地方需要我人工确认\u0026rdquo; 这样 WorkBuddy 会被迫审视自己的输出。有时候它写到第 4 条，会发现\u0026quot;还没验证\u0026quot;的内容有一长串，就会主动补齐。\n我印象最深的一次，是让它帮我整理几百个保险产品的数据。它说做完了，我让它出验收清单，结果它写到第 4 条时列了 7 项没验证的——包括两个数据源不一致、三条聚合逻辑有歧义。\n这几个问题要是上线了才发现，后面要花几倍的时间去修。\n验收清单本身，比你让 WorkBuddy 干的那件事，有时更有价值。\n从装上 WorkBuddy 到现在，将近三个月时间。踩过坑，走过弯路，也试过放弃。\n但让我一直用下来的原因很简单：工具本身确实能干活。问题从来不在工具，在于我没找到跟它协作的正确方式。\n现在回头看，真正让 WorkBuddy 好用的，不是某个神奇的功能，也不是某个特定的 Skill，而是你给它足够清楚的上下文、明确的边界和每次的验收标准。\n如果你刚装上 WorkBuddy，不知道该从哪开始，我的建议是：先别急着让它干活。 花 10 分钟写一份 SOUL.md，把\u0026quot;你的规则\u0026quot;写清楚。这是投入产出比最高的一个动作。\n它不会自己变好。但你会越来越懂怎么用。\n你觉得第几条最有用？你也有跟 AI 协作的踩坑故事吗？留言聊聊。\n相关文章：\n手把手教你造一个Skill——从零开始的保姆级教程（纯干货） WorkBuddy从入门到精通：9个使用技巧让你的AI真正帮你干活 PC端AI办公智能体第一易主：WorkBuddy凭什么两个月干到榜首？ ","permalink":"https://sikinzen.github.io/posts/howtouseworkbuddy1st/","summary":"6 条 WorkBuddy 重度使用经验，每条都是踩坑总结","title":"我重度使用 WorkBuddy 后，总结出 6 条真实经验"},{"content":"一个「反常识」的 AI 技能 2026 年初，英国开发者 Matt Pocock 发布了一个 AI 技能，名叫 Grill Me。整个技能的核心内容，只有短短几行字：\nInterview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree resolving dependencies between decisions one by one. For each question, provide your recommended answer. 没了。就这么点。\n但它火了——从 GitHub 到推特，从 Claude Code 到 OpenClaw，几乎所有支持 Agent Skill 的 AI 工具都在推荐它。\n为什么？因为它解决了一个被大多数人忽视的根本问题：你和 AI 之间最大的瓶颈，不是 AI 的能力，而是你对自己想要什么的理解程度。\n传统方式的困境 我们平时用 AI 的方式通常是这样的：\n\u0026ldquo;帮我写一个用户认证模块。\u0026rdquo;\n然后 AI 噼里啪啦生成一堆代码。你看了觉得不对，说\u0026quot;再改改\u0026quot;。改完还是不对。来回几轮，你累了，勉强接受了。结果上线后发现，根本不是你真正想要的。\n问题出在哪？\n你给 AI 的是一句模糊的指令，而不是一个清晰的需求。 AI 不会追问你：用 JWT 还是 Session？要支持第三方登录吗？密码策略怎么定？Token 过期时间多长？它只是按照自己的\u0026quot;理解\u0026quot;去执行——而这个理解，往往和你的期望差了十万八千里。\n这种\u0026quot;指令模糊 → 产出偏差 → 反复返工\u0026quot;的循环，是绝大多数人使用 AI 的日常。\nGrill Me 的核心哲学 Grill Me 做的事情恰恰相反——它不急于执行，而是先把你问清楚。\n1. 一次只问一个问题 这是最重要的规则。很多 AI 喜欢一口气抛出五六个问题，用户看着就头大，要么挑一两个回答，要么随便糊弄过去。Grill Me 强制一次一问，逼你认真思考每一个决策点。\n2. 每个问题都附带推荐答案 这是神来之笔。面对一个空白的提问，你需要从零开始思考。但如果 AI 问你\u0026quot;建议使用 JWT，因为它无状态且易于横向扩展，你觉得呢？\u0026quot;——你只需要说\u0026quot;同意\u0026quot;或者\u0026quot;不同意，我更倾向 Session 因为……\u0026quot;。\n有靶子，才能开枪。 推荐答案大幅降低了回答的心理负担。\n3. 深度优先，而非广度优先 Grill Me 采用决策树的遍历方式：沿着一条分支一直问到底，问清楚了再切到下一条。这确保了每个问题都被充分探讨，而不是蜻蜓点水式地覆盖一堆表面问题。\n4. 能自己查的就不问 如果一个问题可以通过查看代码库、读文档、搜索资料来回答，Grill Me 会先去做，然后把结论拿给你确认，而不是浪费一轮对话来问你。\n它到底怎么工作的？ 假设你说：\u0026ldquo;我要做一个电商网站的订单系统。\u0026rdquo;\n一个普通的 AI 可能会直接开始设计数据库表结构。但 Grill Me 会这样推进：\nQ1/∞： 你的订单系统需要支持哪些支付方式？推荐答案：先支持微信支付和支付宝，覆盖国内 95% 以上的场景，后续再扩展。你觉得呢？\n你说：\u0026ldquo;同意，先这两个。\u0026rdquo;\nQ2/∞： 订单状态机怎么设计？推荐答案：待支付→已支付→已发货→已完成→已取消，加上退款流程。需要我画个状态图吗？\n你说：\u0026ldquo;需要支持部分退款吗？\u0026rdquo;\n它会继续追问：库存扣减时机？并发订单处理？订单超时自动取消？物流对接？每一个决策点都被逐一展开、讨论、确认。\n一场 Grill Me 对话通常持续 30-45 分钟。 结束时，你手上有一份完整的决策记录——每个选择背后的理由、被排除的替代方案、遗留的开放问题。\n不只是写代码 Matt Pocock 本人说过，Grill Me 远不止用于编程。他用它来：\n规划课程内容 —— 通过 AI 的追问，理清课程的目标受众、知识深度、教学节奏 做商业决策 —— 让 AI 从不同角度质疑你的商业假设 设计系统架构 —— 在动手之前，把所有边界条件和权衡点想清楚 个人规划 —— 甚至用它来探索职业方向 本质上，Grill Me 是一个结构化思考的外部工具。它把\u0026quot;想清楚再动手\u0026quot;这个老生常谈的原则，变成了一套可执行的交互流程。\n更高级的玩法：社区增强版 社区对 Grill Me 做了很多扩展。比如 Satya Janghu 的增强版加入了：\n增强特性 说明 多维度思维透镜 第一性原理、预验尸分析（pre-mortem）、钢铁人论证（steelman）、可逆性评估、五问法（5 Whys）等，隐藏在对话背后，用户感受不到\u0026quot;在被框架分析\u0026quot; 模糊回答检测 如果你的回答含糊不清，它会 push back，而不是礼貌性地接受 半答案稻草人 对不完整的回答，先构建一个\u0026quot;稻草人版本\u0026quot;让你反驳，比从零描述容易得多 会话日志 对话结束后自动生成结构化摘要：意图、约束、关键决策、暴露的假设、待解决问题、明确排除的范围 我的实战经验：在 WorkBuddy 中使用 Grill Me 读完那篇文章后，我也在 WorkBuddy 中配置了 Grill Me 技能。经过几天的实际使用，这里分享几点真实体感：\n最直接的效果：返工大幅减少 以前我让 AI 写一篇博客、搭一个功能模块，经常是\u0026quot;写出来→不满意→改→还不对→再改\u0026quot;的循环。用了 Grill Me 之后，AI 会在开始之前先追问关键决策点，一轮 2-3 个问题，几轮下来需求就清晰了。返工次数从平均 3-4 轮降到 1 轮以内。\n6W2H 框架特别适合复杂任务 我配置的版本采用了 6W2H 追问框架（Why/What/Who/When/Where/Which/How/How much），比原始版的\u0026quot;无限追问\u0026quot;更有结构感。对于涉及多个模块、有截止时间、技术方案不明确的任务，这套框架能把模糊的\u0026quot;我想做 X\u0026quot;拆解成可执行的决策清单。\n一次只问 2-3 个问题是关键设计 原文提到的\u0026quot;一次只问一个问题\u0026quot;是核心原则，但在实际使用中，我发现一次问 2-3 个关联问题效率更高——既不会让用户感到信息过载，又不会把对话拖得太长。关键是每个问题都要附带推荐答案，这样用户只需要做判断题而非填空题。\n风险确认和验收标准容易被忽视，但价值最大 6W2H 问完之后，Grill Me 还会追问\u0026quot;最坏情况是什么？有没有 Plan B？\u0026ldquo;以及\u0026quot;怎么算完成？哪些情况算失败？\u0026quot;。这两个步骤在日常工作中经常被跳过，但恰恰是它们防止了\u0026quot;做完了才发现方向不对\u0026quot;的灾难。\n如何在 WorkBuddy 中配置 如果你也在用 WorkBuddy，可以直接创建一个 Skill：\n文件路径：~/.workbuddy/skills/grill-me/SKILL.md\n核心内容（精简版）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # grill-me \u0026gt; 重要任务开始前，用系统化追问确认清楚再动手。 \u0026gt; 核心理念：30 分钟的追问可以节省 8 小时的返工。 ## 触发条件 - 用户说\u0026#34;开始做\u0026#34;、\u0026#34;开始吧\u0026#34;、\u0026#34;go\u0026#34;、\u0026#34;执行\u0026#34; - 任务涉及多个模块/团队协作 - 技术方案不明确或有多种实现路径 ## 追问流程 1. 6W2H 清单（一次问 2-3 个最重要的问题） 2. 风险与备选方案确认 3. 明确验收标准 4. 输出结构化确认清单 ## 规则 - 一次只问 2-3 个问题，附带推荐答案 - 深度优先：沿着一条分支问到底再切换 - 能自己查的就不问，先做功课再确认 配置完成后，当你在对话中说\u0026quot;开始做\u0026quot;或\u0026quot;执行\u0026rdquo;，AI 就会自动进入追问模式。\n为什么它能火？ 回到最初的问题：一个只有 20 个词的技能，凭什么火遍开发者圈？\n因为它触及了人机协作的本质矛盾：AI 可以执行任何指令，但前提是你给出正确的指令。而大多数人，在\u0026quot;搞清楚自己到底想要什么\u0026quot;这件事上，需要帮助。\nGrill Me 不是让 AI 变得更聪明，而是让你想得更清楚。\n在 AI 能力爆炸的今天，真正的瓶颈从来不在机器那边。它在你这边。\n参考文章：热门Skill研究：Grill-Me，凭什么火遍整个开发者圈？，作者杜倬\n本文结合参考文章与个人在 WorkBuddy 中的实战经验写成，所有使用体感均为真实记录。\n","permalink":"https://sikinzen.github.io/posts/grill-me-devs-popularity/","summary":"解析 Grill Me 火遍开发者圈的原因，分享在 WorkBuddy 中使用追问技能的实际经验","title":"一个只有 20 个词的 AI 技能，凭什么火遍开发者圈？——Grill Me 深度解析与实战"},{"content":" ✅ 本文已于 2026-06-09 在公司内网环境实际验证通过 — 从下载二进制到 MCP 连接绿灯，全程约 15 分钟完成。以下所有步骤均经过实测确认。\n背景：为什么要把 Gitea 接入 AI？ 前面几篇分别解决了沟通数据（微信/企微）和项目管理（禅道）的 AI 化问题。本篇聚焦代码托管环节——Gitea。\nGitea 是轻量级自托管 Git 平台，在企业内网广泛使用。让 AI 接入 Gitea 后，可以：\n📦 管理仓库（\u0026ldquo;帮我创建一个新仓库\u0026rdquo; / \u0026ldquo;Fork XX 项目\u0026rdquo;） 🌿 操作分支（\u0026ldquo;列出所有分支\u0026rdquo; / \u0026ldquo;创建 feature 分支\u0026rdquo;） 📝 管理文件（\u0026ldquo;帮我看看 XX 文件的内容\u0026rdquo; / \u0026ldquo;更新配置文件\u0026rdquo;） 🐛 处理 Issue（\u0026ldquo;查看最近的 Bug Issue\u0026rdquo; / \u0026ldquo;创建一个新 Issue\u0026rdquo;） 🔀 处理 PR（\u0026ldquo;看看有哪些待合并的 PR\u0026rdquo; / \u0026ldquo;帮我创建 PR\u0026rdquo;） 🔍 全局搜索（\u0026ldquo;搜索所有包含 XX 关键词的仓库\u0026rdquo;） 方案选择：gitea-mcp（Gitea 官方出品） Gitea 官方维护的 MCP Server，Go 语言编写，预编译单文件部署，下载即用。\n项目 详情 仓库 https://gitea.com/gitea/gitea-mcp 最新版 v1.3.0（2026-05-14） 语言 Go 工具数 23 个 文件大小 ~4.1 MB（Windows x86_64） 认证方式 Personal Access Token 运行模式 stdio / SSE 许可证 MIT 作者 Lunny Xiao（Gitea 核心维护者） 为什么选 gitea-mcp？ 官方出品 — 由 Gitea 核心维护者开发，API 兼容性有保障 零构建 — 预编译二进制，下载解压即可使用，不需要 Go 环境 功能全面 — 23 个工具覆盖仓库/分支/文件/Issue/PR 全流程 双模式运行 — stdio（简单稳定）和 SSE（支持实时推送）两种模式 技术原理：Gitea REST API + MCP Gitea REST API Gitea 兼容 GitHub API（Swagger 文档路径 /api/swagger），提供完整的 RESTful 接口：\nGET /api/v1/repos/{owner}/{repo} — 获取仓库信息 POST /api/v1/repos/{owner}/{repo}/issues — 创建 Issue GET /api/v1/repos/{owner}/{repo}/pulls — 列出 PR POST /api/v1/repos/{owner}/{repo}/branches — 创建分支 PUT /api/v1/repos/{owner}/{repo}/contents/{path} — 更新文件 认证方式 Gitea 使用 Personal Access Token 认证：\n对比项 说明 认证方式 HTTP Header: Authorization: token \u0026lt;your-token\u0026gt; 权限范围 创建 Token 时可选：repo / read:user / write:issue 等 有效期 默认长期有效，可手动撤销 安全性 比用户名密码更安全，可细粒度控制权限 第一步：下载 gitea-mcp 方式一：直接下载预编译版本（推荐） 从官方发布页下载对应平台的二进制文件：\nhttps://gitea.com/gitea/gitea-mcp/releases 选择 Windows x86_64 版本，下载后解压到指定目录：\n1 2 # 建议存放路径 D:\\Software\\AI\\gitea-mcp\\gitea-mcp.exe 💡 这是最简单的方式——不需要 Go 环境，不需要编译，下载即用。文件仅 ~4.1 MB。\n方式二：源码编译 如果需要自定义修改或预编译版本不适用：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 cd D:\\Software\\AI # 克隆仓库 git clone https://gitea.com/gitea/gitea-mcp.git cd gitea-mcp # 设置 Go 代理（国内加速） set GOPROXY=https://goproxy.cn,direct # 编译 go build -o gitea-mcp.exe . # 编译完成后复制到目标目录 copy gitea-mcp.exe D:\\Software\\AI\\gitea-mcp\\ 第二步：获取 Gitea Access Token 在 Gitea 网页端生成 Personal Access Token：\n登录 Gitea 进入 Settings → Applications → Manage Access Tokens 点击 Generate New Token 填写 Token 名称（如 AI-Assistant） 勾选权限范围： repo — 仓库读写权限 read:user — 读取用户信息 write:issue — Issue 写入权限（按需） write:pull_request — PR 写入权限（按需） 点击 Generate Token 立即复制 Token（只显示一次！） ⚠️ 安全提示：Access Token 等于你的 Gitea 账号权限，切勿泄露。建议初期只勾选读权限，确认稳定后再逐步开放写权限。\n第三步：配置 WorkBuddy MCP 连接 在 WorkBuddy 的 MCP 配置文件中添加 Gitea 服务器。\n配置文件位置 C:\\Users\\\u0026lt;你的用户名\u0026gt;\\.workbuddy\\mcp.json ⚠️ 注意：配置文件路径是 ~/.workbuddy/mcp.json，不是 ~/.workbuddy/.mcp.json（没有前导点号）。如果文件不存在，直接创建即可。\nstdio 模式配置（推荐） 1 2 3 4 5 6 7 8 9 10 11 12 { \u0026#34;mcpServers\u0026#34;: { \u0026#34;gitea\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;D:\\\\Software\\\\AI\\\\gitea-mcp\\\\gitea-mcp.exe\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;-t\u0026#34;, \u0026#34;stdio\u0026#34;, \u0026#34;--host\u0026#34;, \u0026#34;https://你的Gitea地址\u0026#34;, \u0026#34;--token\u0026#34;, \u0026#34;你的Access-Token\u0026#34; ] } } } SSE 模式配置（备选） 适合需要实时事件推送的场景：\n步骤 1 — 启动服务器：\n1 D:\\Software\\AI\\gitea-mcp\\gitea-mcp.exe -t sse --host https://你的Gitea地址 --token 你的Access-Token 步骤 2 — 客户端配置：\n1 2 3 4 5 6 7 { \u0026#34;mcpServers\u0026#34;: { \u0026#34;gitea\u0026#34;: { \u0026#34;url\u0026#34;: \u0026#34;http://localhost:8080/sse\u0026#34; } } } 命令行参数说明 参数 说明 示例 -t 运行模式 stdio（默认）或 sse --host Gitea 服务器地址 https://gitea.example.com --token Personal Access Token a1b2c3d4e5f6... 第四步：信任 MCP 服务器 配置完成后，需要手动信任：\n打开 WorkBuddy 客户端 进入连接器管理页面 在顶部自定义连接器入口找到 gitea 点击信任按钮激活 重启 WorkBuddy 会话使 MCP 生效 23 个工具详解 用户管理（2 个） 工具 功能 典型用法 get_my_user_info 获取已认证用户信息 \u0026ldquo;我的 Gitea 账号信息\u0026rdquo; search_users 搜索用户 \u0026ldquo;搜索用户 XX\u0026rdquo; 仓库管理（3 个） 工具 功能 典型用法 create_repo 创建新仓库 \u0026ldquo;帮我创建一个新仓库\u0026rdquo; fork_repo Fork 现有仓库 \u0026ldquo;Fork XX 项目到我的账号\u0026rdquo; list_my_repos 列出我的所有仓库 \u0026ldquo;我有哪些仓库？\u0026rdquo; 分支管理（3 个） 工具 功能 典型用法 create_branch 创建新分支 \u0026ldquo;从 main 创建 feature 分支\u0026rdquo; delete_branch 删除分支 \u0026ldquo;删除 feature-xx 分支\u0026rdquo; list_branches 列出所有分支 \u0026ldquo;XX 仓库有哪些分支？\u0026rdquo; Commit 管理（1 个） 工具 功能 典型用法 list_repo_commits 查看提交历史 \u0026ldquo;最近的 commit 记录\u0026rdquo; 文件操作（4 个） 工具 功能 典型用法 get_file_content 获取文件内容和元数据 \u0026ldquo;看看 config.yaml 的内容\u0026rdquo; create_file 创建新文件 \u0026ldquo;创建 README.md\u0026rdquo; update_file 更新现有文件 \u0026ldquo;更新配置文件\u0026rdquo; delete_file 删除文件 \u0026ldquo;删除临时文件\u0026rdquo; Issue 管理（4 个） 工具 功能 典型用法 get_issue_by_index 获取特定 Issue 详情 \u0026ldquo;Issue #42 的详情\u0026rdquo; list_repo_issues 列出所有 Issue \u0026ldquo;最近的 Bug Issue\u0026rdquo; create_issue 创建新 Issue \u0026ldquo;创建一个 Bug 报告\u0026rdquo; create_issue_comment 添加 Issue 评论 \u0026ldquo;在 Issue #42 加一条评论\u0026rdquo; PR 管理（3 个） 工具 功能 典型用法 get_pull_request_by_index 获取特定 PR 详情 \u0026ldquo;PR #10 的详情\u0026rdquo; list_repo_pull_requests 列出所有 PR \u0026ldquo;待合并的 PR 有哪些？\u0026rdquo; create_pull_request 创建新 PR \u0026ldquo;帮我创建 PR\u0026rdquo; 搜索与组织（3 个） 工具 功能 典型用法 search_repos 搜索仓库 \u0026ldquo;搜索包含 XX 的仓库\u0026rdquo; search_org_teams 搜索组织团队 \u0026ldquo;XX 组织下有哪些团队？\u0026rdquo; get_gitea_mcp_server_version 获取服务器版本 \u0026ldquo;gitea-mcp 版本\u0026rdquo; 完整架构图 ┌─────────────────────────────────────────────────┐ │ AI 助手 │ │ (WorkBuddy / 其他) │ └──────────────┬──────────────────────────────────┘ │ MCP 协议 (stdio / SSE) ▼ ┌─────────────────────────────────────────────────┐ │ gitea-mcp.exe (Go 单文件, ~4.1 MB) │ │ ┌───────────────────────────────────────────┐ │ │ │ 23 tools │ │ │ │ 仓库 / 分支 / 文件 / Issue / PR / 搜索 │ │ │ └───────────────────────────────────────────┘ │ │ --host + --token (启动参数) │ └──────────────┬──────────────────────────────────┘ │ Gitea REST API (HTTP) │ Personal Access Token 认证 ▼ ┌─────────────────────────────────────────────────┐ │ Gitea 代码托管平台 │ │ (https://你的Gitea地址) │ │ ┌────────────┬────────────┬────────────┐ │ │ │ Repos │ Issues │ PRs │ │ │ ├────────────┼────────────┼────────────┤ │ │ │ Branches │ Files │ Commits │ │ │ └────────────┴────────────┴────────────┘ │ └─────────────────────────────────────────────────┘ Gitea vs Gerrit：两个代码平台 MCP 对比 Gitea 和 Gerrit 虽然都涉及代码管理，但定位完全不同，两者互补：\n对比项 Gitea MCP Gerrit MCP 平台定位 代码托管（类 GitHub） 代码审查（Code Review） 核心功能 仓库/分支/文件/Issue/PR 变更查询/审查/评论/回退 工具数 23 18 语言 Go（预编译单文件） Python（需构建） 认证方式 Access Token HTTP 密码 / git_cookies 部署难度 ⭐ 最低 ⭐⭐ 文件大小 ~4.1 MB Python 项目 AI 使用场景 管理仓库、创建 Issue/PR 代码审查、发布评论 典型配合场景：\n开发者推送代码 → Gitea 仓库收到 Push ↓ Gitea 触发 PR ↓ Gerrit 发起 Code Review ↓ AI 助手同时操作两边： - Gitea: 创建 Issue 记录 Bug - Gerrit: 查看变更 diff，发布审查意见 六系统接入全景对比 对比项 微信 企微 禅道 Gerrit Gitea 数据类型 聊天记录 聊天+组织架构 项目管理 代码审查 代码仓库 接入方式 数据库解密 数据库解密 REST API REST API REST API MCP 工具数 N/A N/A 542 18 23 部署方式 Python 工具链 Python 工具链 Go 编译 Python 构建 下载即用 认证方式 内存密钥 内存密钥 App Token HTTP 密码 Access Token AI 可操作性 只读 只读 读写 读写 读写 部署难度 ⭐⭐⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐ ⭐ 文件体积 工具链 工具链 16 MB Python 项目 ~4.1 MB 核心发现：在所有接入方案中，Gitea 的部署难度最低——官方提供预编译二进制，下载解压即可使用，不需要任何编译或构建步骤。如果团队已经在使用 Gitea，这是最先推荐的 AI 接入方案。\n踩坑实录（已验证） # 遇到的问题 原因 解决方法 1 ✅ Token 权限不足 创建时未勾选所需权限 重新生成 Token，勾选对应权限 2 ✅ Gitea 内网地址不可达 公司 Gitea 在内网 连接公司网络 / VPN 3 ✅ Windows 路径转义 JSON 中 \\ 需转义为 \\\\ 直接使用 \\\\ 双反斜杠 4 ✅ HTTP 协议正常工作 Gitea 使用 HTTP 非 HTTPS --host 参数直接写 http:// 即可，无需 HTTPS 5 未遇到 SSE 模式端口冲突 stdio 模式无此问题 6 未遇到 Token 过期或被撤销 N/A 💡 实测结论：stdio 模式 + HTTP 协议 + Access Token，全流程零踩坑。官方预编译二进制开箱即用，无需任何编译或构建步骤。\n注意事项与风险提示 ⚠️ 使用前请充分了解以下风险：\n代码安全 — Gitea 中的代码属于企业核心资产，AI 读取文件内容时可能接触敏感逻辑和密钥 操作权限 — create_file、update_file、delete_file 等写操作需要谨慎授权 Token 安全 — Access Token 等于你的完整 Gitea 权限，泄露风险极大 审计合规 — 通过 AI 执行的操作应可追溯 内网访问 — 公司 Gitea 通常在内网，需要确保 AI 运行环境可访问 文件误操作 — AI 执行 delete_file 或 update_file 前应确认文件内容 建议：\n初期建议只勾选读权限（read:repository, read:user），确认稳定后再开放写权限 Token 名称标注用途（如 AI-Assistant-ReadOnly），便于管理和审计 定期轮换 Token，不要长期使用同一个 发布博客时，不要暴露真实的 Gitea 地址和 Token 当前状态 环节 状态 备注 方案调研 ✅ 完成 确认 gitea-mcp 为最佳方案 下载预编译二进制 ✅ 完成 v1.3.0，解压即用 获取 Access Token ✅ 完成 在 Gitea 网页端生成 配置 WorkBuddy MCP ✅ 完成 ~/.workbuddy/mcp.json，stdio 模式 信任 MCP 服务器 ✅ 完成 连接器管理页面点击信任，绿灯 API 连通性验证 ✅ 完成 2026-06-09 内网验证通过，get_me 返回用户信息，list_my_repos 返回仓库列表 实际验证记录（2026-06-09） 环境信息 项目 实际值 Gitea 版本 内网自托管实例 网络环境 公司内网 gitea-mcp 版本 v1.3.0（预编译 Windows x86_64） 运行模式 stdio 协议 HTTP（非 HTTPS） 认证用户 sjinqian 验证步骤 API 连通性测试 — 使用 curl 直接调用 Gitea REST API：\n1 curl -H \u0026#34;Authorization: token \u0026lt;your-token\u0026gt;\u0026#34; http://\u0026lt;gitea-host\u0026gt;/api/v1/user 返回 HTTP 200，包含用户信息（login, email, full_name 等）。\n仓库列表测试 — 调用 /api/v1/repos/search 接口：\n1 curl -H \u0026#34;Authorization: token \u0026lt;your-token\u0026gt;\u0026#34; http://\u0026lt;gitea-host\u0026gt;/api/v1/repos/search?limit=5 成功返回仓库列表，包含 AI_Project、jx_devel 等组织下的仓库。\nMCP 工具调用测试 — 在 WorkBuddy 会话中直接使用自然语言：\n\u0026ldquo;我有哪些仓库？\u0026rdquo; → list_my_repos 返回 30 个仓库 \u0026ldquo;我的 Gitea 账号信息\u0026rdquo; → get_me 返回认证用户详情 \u0026ldquo;查看 XT_Android 最近的提交\u0026rdquo; → list_commits 返回完整的 commit 历史 实际使用的 MCP 配置 1 2 3 4 5 6 7 8 9 10 11 12 { \u0026#34;mcpServers\u0026#34;: { \u0026#34;gitea\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;D:\\\\Work\\\\AI\\\\Workspace\\\\Connection\\\\gitea-mcp\\\\gitea-mcp.exe\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;-t\u0026#34;, \u0026#34;stdio\u0026#34;, \u0026#34;--host\u0026#34;, \u0026#34;http://\u0026lt;内网Gitea地址\u0026gt;\u0026#34;, \u0026#34;--token\u0026#34;, \u0026#34;\u0026lt;your-access-token\u0026gt;\u0026#34; ] } } } ⚠️ 上述配置中的地址和 Token 已脱敏，请替换为你自己的实际值。\n系列文章（AI 工具链） 本博客「AI 工具链」系列，教你把各类研发工具接入 AI 助手（悟空）：\n让 AI 管理你的代码仓库：Gitea MCP Server 接入指南 让 AI 帮你管代码评审：Gerrit 接入指南 让 AI 管理你的项目：禅道接入指南 让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南 让 AI 读懂你的企业微信：wechat-decrypt 接入指南 在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战 在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程 ","permalink":"https://sikinzen.github.io/posts/howtoconnectgiteaandai/","summary":"记录将 Gitea 代码托管平台接入 AI 助手的完整过程，使用 Go 编写的 gitea-mcp 作为 MCP Server，实现 AI 直接操作仓库、Issue、PR 等功能","title":"让 AI 管理你的代码仓库：Gitea MCP Server 接入指南"},{"content":" ⚠️ 本文尚未实际验证 — MCP Server 已完成下载和配置，但因 Gerrit 在公司内网，API 连通性需到公司内网环境验证。配置步骤和命令均基于官方文档整理，实际使用时可能需要微调。\n背景：为什么要把 Gerrit 接入 AI？ 前面几篇写了个人微信、企业微信和禅道接入 AI 的方案。本篇聚焦代码审查环节——Gerrit。\nGerrit 是业界广泛使用的代码审查工具，每天产生大量 Change、评论、评审记录。让 AI 接入 Gerrit 后，可以：\n🔍 快速查询变更（\u0026ldquo;帮我看看今天有哪些待审查的 Change\u0026rdquo;） 📝 分析代码差异（\u0026ldquo;帮我看看 CL #12345 改了什么\u0026rdquo;） 💬 发布审查评论（\u0026ldquo;在这个文件的第 42 行加一条审查意见\u0026rdquo;） 👥 推荐审查人（\u0026ldquo;这个项目谁比较熟悉？\u0026quot;） 🔄 管理变更状态（\u0026ldquo;把这个 CL 标记为 WIP\u0026rdquo;） 方案选择：Gerrit MCP Server 经过调研，社区有多个 Gerrit MCP Server 实现：\n项目 语言 工具数 维护者 特点 GerritCodeReview/gerrit-mcp-server Python 18 Google 官方镜像 功能全面，认证灵活 siarhei-belavus/gerrit-mcp Node.js ~10 社区 轻量，专注代码审查 cayirtepeomer/gerrit-code-review-mcp Python ~8 社区 简洁，适合基础场景 选择 GerritCodeReview/gerrit-mcp-server 的理由：\nGoogle 官方镜像 — 与 Gerrit 项目同属一个组织，API 兼容性有保障 工具最全 — 18 个工具覆盖变更查询、审查、评论、状态管理 认证灵活 — 支持 git_cookies、http_basic、gob_curl 三种方式 双模式运行 — stdio（按需启动）和 HTTP 服务器（持久化）两种模式 技术原理：Gerrit REST API + MCP Gerrit REST API Gerrit 提供了完整的 REST API（默认路径 /a/），支持 JSON 格式的请求和响应。核心功能包括：\nGET /a/changes/ — 查询变更列表 GET /a/changes/{change-id} — 获取变更详情 GET /a/changes/{id}/detail — 获取变更详细信息 POST /a/changes/{id}/revisions/{r}/review — 发布审查 POST /a/changes/{id}/abandon — 放弃变更 认证方式 Gerrit REST API 支持三种认证方式：\n认证方式 说明 推荐度 git_cookies 复用 Git 的 .gitcookies 文件 ⭐⭐⭐ 推荐 http_basic 用户名 + HTTP 密码 ⭐⭐ 简单直接 gob_curl Google 内部专用 — 不适用 我们选择 http_basic 认证，因为配置最简单，不需要额外文件。如果团队已配置 .gitcookies，推荐使用 git_cookies 方式。\n第一步：安装前置依赖 gerrit-mcp-server 需要 Python 3.11+ 和 curl：\n1 2 3 4 5 6 7 # 检查 Python 版本 python --version # 需要 Python 3.11+ # 检查 curl curl --version # 大多数系统已预装 第二步：克隆并构建 1 2 3 4 5 6 7 8 9 cd D:\\Software\\AI # 克隆仓库 git clone https://github.com/GerritCodeReview/gerrit-mcp-server.git cd gerrit-mcp-server # 构建项目（创建虚拟环境 + 安装依赖） # Windows 下使用 Git Bash 或 WSL 运行构建脚本 bash build-gerrit.sh 构建脚本会：\n创建 Python 虚拟环境（.venv/） 安装所有依赖（mcp, httpx 等） 使服务器就绪 第三步：配置 Gerrit 连接 在 gerrit_mcp_server/ 目录下创建配置文件：\n1 2 # 复制示例配置 cp gerrit_mcp_server/gerrit_config.sample.json gerrit_mcp_server/gerrit_config.json 配置内容 方式一：http_basic 认证（推荐入门） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 { \u0026#34;default_gerrit_base_url\u0026#34;: \u0026#34;https://你的Gerrit地址/\u0026#34;, \u0026#34;gerrit_hosts\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;Company Gerrit\u0026#34;, \u0026#34;external_url\u0026#34;: \u0026#34;https://你的Gerrit地址/\u0026#34;, \u0026#34;authentication\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;http_basic\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;你的用户名\u0026#34;, \u0026#34;auth_token\u0026#34;: \u0026#34;你的HTTP密码\u0026#34; } } ] } 方式二：git_cookies 认证（推荐已有 Git 配置的团队） 1 2 3 4 5 6 7 8 9 10 11 12 13 { \u0026#34;default_gerrit_base_url\u0026#34;: \u0026#34;https://你的Gerrit地址/\u0026#34;, \u0026#34;gerrit_hosts\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;Company Gerrit\u0026#34;, \u0026#34;external_url\u0026#34;: \u0026#34;https://你的Gerrit地址/\u0026#34;, \u0026#34;authentication\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;git_cookies\u0026#34;, \u0026#34;gitcookies_path\u0026#34;: \u0026#34;~/.gitcookies\u0026#34; } } ] } 获取 HTTP 密码 登录 Gerrit 网页端 进入 Settings → HTTP Credentials 点击 Generate Password 复制生成的密码，填入 auth_token 字段 ⚠️ 安全提示：HTTP 密码是敏感信息，不要提交到版本库。配置文件 gerrit_config.json 应加入 .gitignore。\n第四步：配置 WorkBuddy MCP 连接 在 WorkBuddy 的 MCP 配置文件中添加 Gerrit 服务器。\n配置文件位置 C:\\Users\\\u0026lt;你的用户名\u0026gt;\\.workbuddy\\.mcp.json stdio 模式配置（推荐） gerrit-mcp-server 支持两种运行模式，stdio 模式最简单：\n1 2 3 4 5 6 7 8 9 10 11 { \u0026#34;mcpServers\u0026#34;: { \u0026#34;gerrit\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;python\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;D:\\\\Software\\\\AI\\\\gerrit-mcp-server\\\\gerrit_mcp_server\\\\main.py\u0026#34; ], \u0026#34;cwd\u0026#34;: \u0026#34;D:\\\\Software\\\\AI\\\\gerrit-mcp-server\u0026#34; } } } 💡 注意：gerrit-mcp-server 不通过环境变量传递配置，而是读取 gerrit_mcp_server/gerrit_config.json 文件。cwd 设置为项目根目录，确保能找到配置文件。\nHTTP 模式配置（备选） 如果需要持久化运行，可以用 HTTP 模式：\n1 2 3 4 5 6 7 8 # 启动服务器 bash server.sh start # 检查状态 bash server.sh status # 停止服务器 bash server.sh stop 客户端配置：\n1 2 3 4 5 6 7 { \u0026#34;mcpServers\u0026#34;: { \u0026#34;gerrit\u0026#34;: { \u0026#34;url\u0026#34;: \u0026#34;http://localhost:8080/sse\u0026#34; } } } 第五步：信任 MCP 服务器 配置完成后，需要手动信任：\n打开 WorkBuddy 客户端 进入连接器管理页面 在顶部自定义连接器入口找到 gerrit 点击信任按钮激活 重启 WorkBuddy 会话使 MCP 生效 18 个工具详解 变更查询与检索（5 个） 工具 功能 典型用法 query_changes 按查询字符串搜索变更 \u0026ldquo;查看所有 open 状态的 Change\u0026rdquo; query_changes_by_date_and_filters 按日期范围+项目+状态搜索 \u0026ldquo;本周我提交的 Change\u0026rdquo; get_change_details 获取单个 Change 完整摘要 \u0026ldquo;CL #12345 的详细信息\u0026rdquo; get_most_recent_cl 获取用户最近的 Change \u0026ldquo;我最新提交的 Change\u0026rdquo; changes_submitted_together 查看与指定 CL 一起提交的变更 \u0026ldquo;这个 CL 还关联了哪些提交？\u0026rdquo; 提交消息与文件（3 个） 工具 功能 典型用法 get_commit_message 获取提交消息 \u0026ldquo;CL #12345 的 commit message\u0026rdquo; list_change_files 列出变更涉及的所有文件 \u0026ldquo;这个 CL 修改了哪些文件？\u0026rdquo; get_file_diff 获取指定文件的 diff \u0026ldquo;看看 main.py 的具体改动\u0026rdquo; 评审与评论（4 个） 工具 功能 典型用法 list_change_comments 查看变更的所有评论 \u0026ldquo;CL #12345 有哪些审查意见？\u0026rdquo; post_review_comment 在文件特定行发布评论 \u0026ldquo;在第 42 行加一条审查意见\u0026rdquo; suggest_reviewers 推荐审查人 \u0026ldquo;这个项目推荐谁做审查？\u0026rdquo; add_reviewer 添加审查人/抄送人 \u0026ldquo;把张三加为审查人\u0026rdquo; 变更状态操作（5 个） 工具 功能 典型用法 set_ready_for_review 标记为待审查 \u0026ldquo;CL 准备好了，提交审查\u0026rdquo; set_work_in_progress 标记为进行中（WIP） \u0026ldquo;先标记 WIP，还没写完\u0026rdquo; abandon_change 放弃变更 \u0026ldquo;这个 Change 不要了\u0026rdquo; revert_change 回退单个变更 \u0026ldquo;回退 CL #12345\u0026rdquo; revert_submission 回退整个提交 \u0026ldquo;回退整个提交（含多个 CL）\u0026rdquo; 变更创建与管理（2 个） 工具 功能 典型用法 create_change 创建新变更 \u0026ldquo;帮我创建一个 CL\u0026rdquo; set_topic 设置/删除变更主题 \u0026ldquo;给这个 CL 设置 topic\u0026rdquo; 其他（1 个） 工具 功能 典型用法 get_bugs_from_cl 从提交消息提取 Bug ID \u0026ldquo;这个 CL 关联了哪些 Bug？\u0026rdquo; 完整架构图 ┌─────────────────────────────────────────────────┐ │ AI 助手 │ │ (WorkBuddy / 其他) │ └──────────────┬──────────────────────────────────┘ │ MCP 协议 (stdio / HTTP SSE) ▼ ┌─────────────────────────────────────────────────┐ │ gerrit-mcp-server (Python 3.11+) │ │ ┌───────────────────────────────────────────┐ │ │ │ 18 tools │ │ │ │ 变更查询 / 文件差异 / 审查评论 / 状态管理 │ │ │ └───────────────────────────────────────────┘ │ │ gerrit_config.json (认证配置) │ └──────────────┬──────────────────────────────────┘ │ Gerrit REST API (HTTP) │ http_basic / git_cookies 认证 ▼ ┌─────────────────────────────────────────────────┐ │ Gerrit 代码审查系统 │ │ (https://你的Gerrit地址/) │ │ ┌────────────┬────────────┬────────────┐ │ │ │ Change │ Review │ Comment │ │ │ ├────────────┼────────────┼────────────┤ │ │ │ Patch Set │ Merge │ Abandon │ │ │ └────────────┴────────────┴────────────┘ │ └─────────────────────────────────────────────────┘ 五系统接入对比 对比项 微信 企微 禅道 Gerrit Gitea 数据类型 聊天记录 聊天+组织架构 项目管理 代码审查 代码仓库 接入方式 数据库解密 数据库解密 REST API REST API REST API 加密难度 ⭐⭐⭐⭐ ⭐⭐ ⭐ ⭐ ⭐ MCP 工具数 N/A N/A 542 18 23 认证方式 内存密钥 内存密钥 App Token HTTP 密码/git_cookies Access Token AI 可操作性 只读 只读 读写 读写 读写 部署难度 ⭐⭐⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐ ⭐ 语言 Go + Python Python Go Python Go 核心区别：Gerrit 和 Gitea 都是\u0026quot;官方 API 对接\u0026rdquo;，但侧重点不同——Gerrit 聚焦代码审查流程（变更、评论、评审），Gitea 聚焦代码仓库管理（仓库、分支、文件、Issue、PR）。两者互补，配合使用可覆盖完整的代码协作流程。\n踩坑预判 # 可能的问题 原因 预期解决方法 1 build-gerrit.sh 在 Windows 下无法运行 构建脚本是 Bash 脚本 使用 Git Bash 或 WSL 运行 2 Python 版本不兼容 需要 Python 3.11+ 安装合适版本或使用 venv 3 Gerrit 内网地址不可达 和禅道一样的内网访问问题 公司网络 / VPN 4 HTTP 密码生成失败 部分 Gerrit 版本不支持 HTTP 认证 改用 git_cookies 认证 5 gerrit_config.json 路径问题 MCP 启动时工作目录不正确 确保设置 cwd 为项目根目录 6 curl 命令不可用 Windows 默认可能没有 curl Git Bash 自带 curl，或手动安装 注意事项与风险提示 ⚠️ 使用前请充分了解以下风险：\n代码安全 — Gerrit 中的代码属于企业核心资产，AI 读取 diff 时可能接触敏感逻辑 操作权限 — post_review_comment、abandon_change 等写操作需要谨慎授权 凭据安全 — HTTP 密码 / git_cookies 等于 Gerrit 的完整访问权限，切勿泄露 审计合规 — 通过 AI 执行的操作应可追溯，建议初期只开放读权限 内网访问 — 公司 Gerrit 通常在内网，需要确保 AI 运行环境可访问 版本兼容 — gerrit-mcp-server 基于 Gerrit REST API v2/v3，低版本可能不完全兼容 建议：\n初期建议只开放读权限（查询变更、查看 diff、列出评论） 写操作（发布评论、放弃变更等）需人工确认后再执行 配置文件 gerrit_config.json 不要提交到版本库 发布博客时，不要暴露真实的 Gerrit 地址、用户名、密码 当前状态 环节 状态 备注 方案调研 ✅ 完成 对比了 3 个 Gerrit MCP Server 下载构建 ⏳ 待执行 需在公司内网环境操作 配置 gerrit_config.json ⏳ 待执行 需要 Gerrit 地址和认证信息 配置 WorkBuddy MCP ⏳ 待执行 需先完成构建 信任 MCP 服务器 ⏳ 待执行 需先完成配置 API 连通性验证 ⏳ 待执行 需要公司内网环境 创建 Skill ⏳ 待执行 验证通过后创建 🔔 提醒：本文所有步骤均基于官方文档整理，尚未在实际 Gerrit 环境中验证。到公司内网后，请按照步骤逐一执行并验证。\n系列文章（AI 工具链） 本博客「AI 工具链」系列，教你把各类研发工具接入 AI 助手（悟空）：\n让 AI 管理你的代码仓库：Gitea MCP Server 接入指南 让 AI 帮你管代码评审：Gerrit 接入指南 让 AI 管理你的项目：禅道接入指南 让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南 让 AI 读懂你的企业微信：wechat-decrypt 接入指南 在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战 在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程 ","permalink":"https://sikinzen.github.io/posts/howtoconnectgerritandai/","summary":"记录将 Gerrit 代码审查系统接入 AI 助手的完整过程，使用 Python 编写的 gerrit-mcp-server 作为 MCP Server，实现 AI 直接查询变更、发布审查评论、管理代码评审","title":"让 AI 参与代码审查：Gerrit MCP Server 接入指南"},{"content":"背景：为什么要让 AI 读企业微信？ 上一篇写了个人微信接入 AI 的指南，解决的是社交沟通数据的 AI 化问题。但对于日常办公来说，企业微信才是主战场——项目协调、需求确认、问题排查、跨部门沟通……大量关键信息沉淀在企微的聊天记录里。\n如果你用的 AI 助手能直接读取企微聊天记录，就可以：\n📋 自动整理每日工作事项（不用自己翻聊天逐条总结） 🔍 跨群搜索项目关键信息（项目讨论散落在 N 个群里） 👥 查询组织架构和联系人（\u0026ldquo;帮我看看 XX 部门有谁\u0026rdquo;） 📊 统计沟通频率和活跃时段（谁最活跃？哪些群最忙？） 好消息是：企业微信比个人微信更容易打通。为什么？往下看。\n先说结论：企微 vs 个人微信 对比项 个人微信 企业微信 加密方式 SQLCipher 4 (AES-256-CBC + HMAC-SHA512) wxSQLite3 (AES-128-CBC) 密钥长度 32 字节（256 bit） 16 字节（128 bit） 密钥数量 每个数据库文件独立密钥（需 PBKDF2 派生） 一个总密钥解密所有库 密钥提取难度 高（4.1+ 需 DLL 注入 + PBKDF2 派生） 低（直接内存结构体扫描） 解密验证 有 HMAC 校验（双重验证） 无 HMAC（单层加密） 社区评价 — 看雪论坛：\u0026ldquo;企业微信是所有同类产品里解密最简单的\u0026rdquo; 配置难度 ⭐⭐⭐⭐ ⭐⭐ 简单来说：个人微信 4.1+ 需要三步（DLL 注入 → 提取 passphrase → PBKDF2 派生），企微只需要一步（内存扫描）。\n技术原理：企微本地数据库是怎么加密的？ 加密算法 企业微信使用 wxSQLite3（SQLite 的 AES 加密扩展），比个人微信的 SQLCipher 简单得多：\n加密算法：AES-128-CBC（每页独立派生 key/IV） Key 派生：page_index + \u0026#34;sAlT\u0026#34; → AES key IV 派生：page_index → IV 密钥长度：16 字节（128 bit） HMAC 校验：无（比 SQLCipher 少一层验证） 密钥存储 有一个全局总密钥，存在 WXWork 进程的内存中 每个数据库文件在打开时，由总密钥 + page_index 派生出每页的加密 key 和 IV 总密钥可以直接从进程内存中扫描提取——不需要像个人微信 4.1+ 那样做 PBKDF2 派生 数据目录 C:\\Users\\\u0026lt;用户名\u0026gt;\\Documents\\WXWork\\\u0026lt;账号ID\u0026gt;\\Data\\ ├── message.db # 聊天消息（核心） ├── session.db # 会话列表 ├── user.db # 联系人/同事信息 ├── message_lookup.db # 消息索引 ├── file.db # 文件传输记录 ├── company.db # 企业信息 ├── calendar_r7.db # 日程信息 ├── crm.db # CRM 数据 └── ... ⚠️ 注意：\u0026lt;账号ID\u0026gt; 是一串数字，如果你的企微有多个上下文（如主账号 + 应用账号），会有多个这样的目录。\n第一步：下载 wechat-decrypt wechat-decrypt 是一个同时支持个人微信 4.x 和企业微信 5.x 的解密工具，功能比 wechat-cli 更全面。\n1 2 3 cd D:\\Software\\AI git clone https://github.com/ylytdeng/wechat-decrypt.git cd wechat-decrypt 安装 Python 依赖 1 2 # 如果你用 WorkBuddy，需要安装到它自己的 Python 环境 C:\\ProgramData\\WorkBuddy\\chromium-env\\6npn1c\\.workbuddy\\binaries\\python\\versions\\3.13.12\\python.exe -m pip install pycryptodomex zstandard mcp pillow flask tqdm ⚠️ 坑：项目自带的 requirements.txt 在 Git 克隆时可能因为编码问题损坏（中文注释导致 UTF-8 解码失败）。如果遇到 MetadataGenerationFailed 错误，手动重写 requirements.txt 或直接用上面的命令安装即可。\n验证依赖安装：\n1 python -c \u0026#34;import Cryptodome; import zstandard; import mcp; import PIL; import flask; import tqdm; print(\u0026#39;All dependencies OK\u0026#39;)\u0026#34; 第二步：配置数据目录 企微可能有多个数据目录（同一个企微账号的不同上下文），需要先确认哪个是主账号。\n创建配置文件 config.json，指定主账号的数据目录：\n1 2 3 { \u0026#34;wxwork_db_dir\u0026#34;: \u0026#34;C:\\\\Users\\\\\u0026lt;你的用户名\u0026gt;\\\\Documents\\\\WXWork\\\\\u0026lt;你的账号ID\u0026gt;\\\\Data\u0026#34; } 💡 如何判断哪个是主账号？ 统计每个 Data 目录下的 .db 文件数量和加密数量，最多的那个通常是主账号。可以用这个脚本快速检查：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import os docs = r\u0026#39;C:\\Users\\\u0026lt;你的用户名\u0026gt;\\Documents\\WXWork\u0026#39; for name in os.listdir(docs): data_dir = os.path.join(docs, name, \u0026#39;Data\u0026#39;) if not os.path.isdir(data_dir): continue encrypted = 0 plain = 0 total = 0 for fname in os.listdir(data_dir): if not fname.endswith(\u0026#39;.db\u0026#39;): continue fpath = os.path.join(data_dir, fname) if os.path.getsize(fpath) \u0026lt; 4096: continue total += 1 with open(fpath, \u0026#39;rb\u0026#39;) as f: header = f.read(16) if header == b\u0026#39;SQLite format 3\\x00\u0026#39;: plain += 1 else: encrypted += 1 if total \u0026gt; 0: print(f\u0026#39;{name}: total={total}, encrypted={encrypted}, plain={plain}\u0026#39;) 第三步：提取企微密钥（一步搞定！） 前提条件 企业微信客户端正在运行并已登录 命令行以管理员权限运行 运行密钥提取 1 2 cd D:\\Software\\AI\\wechat-decrypt python find_wxwork_keys.py 输出示例：\nFound WXWork process: PID=12345 Scanning memory for cipher keys... Found 1 key, verifying against 17 encrypted databases... ✓ Key verification: 15/15 salts matched (15/17 databases are encrypted) Keys saved to wxwork_keys.json 15/15 全部匹配！ 一个 16 字节的 raw key 解密了所有 17 个数据库。\n跟个人微信对比一下：\n个人微信 4.1+：需要 wx_key DLL 注入 → 提取 passphrase → PBKDF2 派生 → 18 个密钥 企微 5.x：一个命令，一个密钥，搞定一切 为什么这么简单？ find_wxwork_keys.py 的实现原理：\n通过 tasklist 找到 WXWork.exe 进程 使用 Windows API ReadProcessMemory 扫描进程内存 在内存中搜索 wxSQLite3 的 cipher 对象结构体 从结构体中提取 16 字节的 raw key 用这个 key 对每个数据库的第一个 salt 做 AES 解密验证 因为企微是 32-bit 进程（指针大小 4 字节），且 wxSQLite3 的 cipher 结构体特征明显，所以扫描成功率极高。\n第四步：解密数据库 1 python decrypt_wxwork_db.py 输出示例：\nDecrypting 17 encrypted databases... ✓ message.db ✓ session.db ✓ user.db ✓ message_lookup.db ✓ file.db ... (more) ✓ calendar_r7.db ✓ crm.db Copied 2 plain databases (no encryption). Result: 17 decrypted, 2 copied, 0 failed. Output directory: wxwork_decrypted/ 17 个加密库全部解密，2 个明文库直接复制，0 失败。 🎉\n解密后的数据库是标准 SQLite 格式，可以用任何 SQLite 工具打开。\n第五步：验证数据与导出消息 列出所有会话 1 python export_wxwork_messages.py --list 输出示例：\nR:xxxxxxxxxxxxx 1824 2026-06-06 15:05 群聊 [项目A沟通群] MAIL 28587 2026-06-06 15:04 应用 企业邮箱 R:xxxxxxxxxxxxx 622 2026-06-06 13:42 群聊 [技术支持群] S:xxxxxxxxxx_xxxxxxxxxx 36 2026-06-05 17:30 单聊 [同事A] ... --- 605 conversations with messages --- 数百个企微会话全部可读！\n会话 ID 格式说明：\nR:\u0026lt;数字\u0026gt; — 群聊 S:\u0026lt;数字\u0026gt;_\u0026lt;数字\u0026gt; — 单聊 O:\u0026lt;数字\u0026gt; — 应用/公众号 MAIL — 企业邮箱 导出聊天记录 1 2 3 4 5 6 7 8 # 导出为 JSON python export_wxwork_messages.py --conversation \u0026#34;S:xxxxxxxxxx_xxxxxxxxxx\u0026#34; --formats json # 导出为 HTML（可以在浏览器里看） python export_wxwork_messages.py --conversation \u0026#34;R:xxxxxxxxxxxxx\u0026#34; --formats html # 同时导出多种格式 python export_wxwork_messages.py --conversation \u0026#34;MAIL\u0026#34; --formats json,csv,html 直接 SQL 查询（高级用法） 解密后的数据库是标准 SQLite，可以直接查询：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import sqlite3 conn = sqlite3.connect(r\u0026#39;D:\\Software\\AI\\wechat-decrypt\\wxwork_decrypted\\message.db\u0026#39;) cur = conn.cursor() # 查询今天的所有消息 cur.execute(\u0026#39;\u0026#39;\u0026#39; SELECT conversation_id, sender_id, content_type, datetime(send_time, \u0026#39;unixepoch\u0026#39;, \u0026#39;localtime\u0026#39;) as time FROM message_table WHERE date(send_time, \u0026#39;unixepoch\u0026#39;, \u0026#39;localtime\u0026#39;) = \u0026#39;2026-06-06\u0026#39; ORDER BY send_time DESC \u0026#39;\u0026#39;\u0026#39;) for row in cur.fetchall(): print(f\u0026#39;[{row[3]}] conv={row[0]}, sender={row[1]}, type={row[2]}\u0026#39;) conn.close() ⚠️ 注意：企微消息的 content 字段是二进制编码（类似 protobuf），直接读取会得到乱码。需要在应用层做 UTF-8 提取，详见后面的「踩坑记录」。\n第六步：配置为 AI 助手 Skill 为了让 AI 助手更方便地调用，可以把企微的查询流程配置成 Skill（以 WorkBuddy 为例）。\nSkill 定义 在 ~/.workbuddy/skills/wecom-cli/SKILL.md 中定义，包含：\n初始化流程：密钥提取 + 数据库解密的完整命令 可用命令：列出会话、导出消息、SQL 查询 数据库说明：每个 .db 文件的用途 消息类型对照表：content_type 与消息类型的映射 使用场景示例：常见场景的命令模板 故障排除：常见问题的解决方案 使用方式 配置好 Skill 后，直接在 AI 助手对话中说：\n@skill:wecom-cli 列出我企微最近 10 个会话 @skill:wecom-cli 帮我导出「项目A沟通群」的聊天记录 @skill:wecom-cli 查看与同事A的单聊最近 20 条消息 @skill:wecom-cli 整理今天企微都有哪些事情 AI 助手会自动调用解密后的数据库，查询并整理结果。\n实战：AI 整理今日企微事项 配置好之后，试了一下让 AI 整理今天企微的所有工作事项：\n@skill:wecom-cli 帮我整理下企微今天都有哪些事情？ AI 自动查询了当天的所有会话和消息，整理出了：\n优先级 事项 来源 🔴 紧急 [版本A] 今日合入发布版本 [项目群] 🟡 重要 [事项B] 方案确认 [客户沟通群] 🟡 重要 [测试任务] 输出测试结果 [团队协作群] 🟢 一般 [版本C] 邮件释放 [技术讨论群] 🟢 一般 [物料D] 寄送安排 [项目管理群] 甚至还能查询组织架构（将真实部门/人名替换为占位符）：\nAI 从 user.db 中查询了部门-人员关联，整理出了完整的组织架构： [部门A]（[你的职位]） ├── [子部门A]（[主管A]）N人 ├── [子部门B]（[主管B]）N人 ├── [子部门C]（[主管C]）N人 └── [子部门D]（[主管D]）N人 🔒 隐私说明：组织架构信息属于企业内部数据，上述示例已做脱敏处理。实际使用时请注意不要将敏感信息发布到公开渠道。\n完整架构图 ┌─────────────────────────────────────────────────┐ │ AI 助手 │ │ (WorkBuddy / 其他) │ └──────────────┬──────────────────────────────────┘ │ 调用 Skill ▼ ┌─────────────────────────────────────────────────┐ │ wecom-cli Skill │ │ ┌───────────────────────────────────────────┐ │ │ │ 命令：--list / --conversation / SQL │ │ │ └───────────────────────────────────────────┘ │ └──────────────┬──────────────────────────────────┘ │ 读取解密数据库 ▼ ┌─────────────────────────────────────────────────┐ │ wxwork_decrypted/ (SQLite 明文) │ │ message.db / session.db / user.db / ... │ └──────────────┬──────────────────────────────────┘ │ 解密来源 ▼ ┌─────────────────────────────────────────────────┐ │ wxwork_keys.json (16 字节 raw key) │ │ (一个密钥解密所有数据库) │ └──────────────┬──────────────────────────────────┘ │ 密钥来源（密钥失效时需重新提取） ▼ ┌─────────────────────────────────────────────────┐ │ find_wxwork_keys.py (内存扫描) │ │ 扫描 WXWork 进程中的 cipher 结构体 │ └──────────────┬──────────────────────────────────┘ │ 读取企微进程内存 ▼ ┌─────────────────────────────────────────────────┐ │ WXWork 5.x 进程内存 │ │ (32-bit, cipher 对象特征明显) │ └──────────────┬──────────────────────────────────┘ │ 加密存储 ▼ ┌─────────────────────────────────────────────────┐ │ 企微本地数据库 (wxSQLite3 加密) │ │ message.db / session.db / user.db / ... │ │ (AES-128-CBC, 无 HMAC, 一个总密钥) │ └─────────────────────────────────────────────────┘ 踩坑记录 # 问题 根本原因 解决方法 1 requirements.txt 安装报错 MetadataGenerationFailed 文件含中文注释，Git 克隆时编码损坏 手动重写 requirements.txt 或直接 pip install 指定包 2 find_wxwork_keys.py 自动检测不到数据目录 脚本默认搜索路径与实际路径不匹配 创建 config.json 手动指定 wxwork_db_dir 3 消息内容显示为 \u0026lt;binary\u0026gt; content 字段是 protobuf 编码的二进制数据 需要做 UTF-8 解码 + 正则提取可读文本 4 联系人 ID 无法映射到姓名 user_id 是数字，需要从关联表查询 查 user_table 获取姓名信息 5 有多个 WXWork 数据目录 同一企微账号有多个上下文 统计 .db 文件数最多的那个是主账号 重点展开：问题 #3 —— 企微消息的二进制编码 企微的消息 content 字段不像个人微信那样直接存纯文本，而是用了类似 protobuf 的二进制编码。直接 str() 会得到 \u0026lt;binary\u0026gt;。\n解决思路：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import re def extract_text(data): \u0026#34;\u0026#34;\u0026#34;从企微二进制消息中提取可读文本\u0026#34;\u0026#34;\u0026#34; if data is None: return \u0026#39;\u0026#39; if isinstance(data, str): return data.strip() if isinstance(data, bytes): # 先尝试整体 UTF-8 解码 try: s = data.decode(\u0026#39;utf-8\u0026#39;) # 提取所有连续的中文/ASCII/标点 parts = re.findall( r\u0026#39;[\\u4e00-\\u9fff\\u3000-\\u303f\\uff00-\\uffef\u0026#39; r\u0026#39;a-zA-Z0-9.,;:!?()\\u2014\\u2018-\\u201d、。，；：！？\u0026#39; r\u0026#39;（）【】《》\u0026#34;\u0026#34;\\\u0026#39;\\\\-]+\u0026#39;, s) result = \u0026#39; \u0026#39;.join(parts).strip() if result: return result except: pass # 逐字节 UTF-8 解码 result = [] i = 0 while i \u0026lt; len(data): if data[i] \u0026lt; 0x20: i += 1 continue if data[i] \u0026lt; 0x80: result.append(chr(data[i])) i += 1 elif data[i] \u0026lt; 0xE0: try: result.append(data[i:i+2].decode(\u0026#39;utf-8\u0026#39;)) i += 2 except: i += 1 elif data[i] \u0026lt; 0xF0: try: result.append(data[i:i+3].decode(\u0026#39;utf-8\u0026#39;)) i += 3 except: i += 1 else: i += 1 return \u0026#39;\u0026#39;.join(result).strip() return str(data) 对比：两套方案的完整流程 个人微信（WeChat 4.1+） 1. 安装 wechat-cli 2. wechat-cli init → 失败 (0/18 salts) 3. 下载 wx_key (DLL 注入工具) 4. 运行 wx_key.exe → 提取 passphrase 5. PBKDF2 派生 → 为每个数据库独立派生密钥 6. 生成 all_keys.json (18 个密钥) 7. wechat-cli 正常使用 关键障碍： WeChat 4.1+ 不再缓存派生后的密钥，passphrase 需要经过 PBKDF2-HMAC-SHA512（256000 次迭代）才能得到实际加密密钥。\n企业微信（WeCom 5.x） 1. 安装 wechat-decrypt 2. 配置 config.json (数据目录) 3. find_wxwork_keys.py → 一步提取密钥 4. decrypt_wxwork_db.py → 一键解密 5. 直接查询或导出 核心优势： 一个 16 字节的 raw key 就能解密所有数据库，无需额外的密钥派生步骤。\n注意事项与风险提示 ⚠️ 使用前请充分了解以下风险：\n违反服务条款 — 读取企微本地数据库可能违反企业微信服务条款 企业风险 — 企业有权对使用第三方工具的账号进行限制 版本兼容 — 企微更新后工具可能失效，需要等待社区适配 密钥时效 — 密钥在企微重启后可能改变，需重新提取 数据敏感 — 企微数据可能包含商业机密和客户信息，务必谨慎处理 隐私保护 — 即使是个人的企微数据，也包含与同事、客户的沟通记录，发布相关内容前请务必脱敏 建议：\n仅在个人学习研究目的下使用 不要在处理重要商业机密的账号上使用 解密后的数据库妥善保管，不要上传到公共平台 发布博客或文章时，务必将所有客户名、项目名称、人员姓名、组织架构信息做脱敏处理 了解并接受可能的后果 总结 环节 工具 难度 备注 安装 wechat-decrypt git + pip ⭐ 注意 requirements.txt 编码问题 配置数据目录 config.json ⭐ 主账号通常是 .db 文件最多的那个 提取密钥 find_wxwork_keys.py ⭐ 一步完成，无需 PBKDF2 解密数据库 decrypt_wxwork_db.py ⭐ 一键解密，0 失败率 导出消息 export_wxwork_messages.py ⭐⭐ 支持 JSON/CSV/HTML 配置 AI Skill SKILL.md ⭐⭐ 一次配置，长期使用 日常使用 对话式查询 ⭐ 密钥失效时需重新提取 核心收获： 企业微信的 wxSQLite3 加密体系比个人微信的 SQLCipher 4 简单得多——没有 PBKDF2 派生，没有 HMAC 校验，一个内存扫描提取的 raw key 就能解密所有数据库。如果你同时需要打通个人微信和企微，建议先从企微开始，成功后再处理个人微信的密钥派生问题。\n系列文章（AI 工具链） 本博客「AI 工具链」系列，教你把各类研发工具接入 AI 助手（悟空）：\n让 AI 管理你的代码仓库：Gitea MCP Server 接入指南 让 AI 帮你管代码评审：Gerrit 接入指南 让 AI 管理你的项目：禅道接入指南 让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南 让 AI 读懂你的企业微信：wechat-decrypt 接入指南 在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战 在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程 ","permalink":"https://sikinzen.github.io/posts/howtoconnectwecomandai/","summary":"记录将企业微信聊天记录接入 AI 的完整过程，对比个人微信的接入方案，展示企微 wxSQLite3 加密与一键解密的实操步骤","title":"让 AI 读懂你的企业微信：wechat-decrypt 接入指南"},{"content":"背景：为什么要把微信接入 AI？ 日常工作中，大量沟通在微信里完成——项目讨论、需求确认、问题排查……这些信息分散在不同聊天窗口，很难回顾和整理。\n如果能让我用的 AI 助手（WorkBuddy）直接读取微信聊天记录，就可以：\n📋 自动整理会议讨论要点 🔍 跨聊天搜索关键信息（不用再一条条翻记录） 📊 统计沟通频率和活跃时段 💾 导出重要对话存档 听起来很美好，但微信的聊天数据库是加密的，直接读取并不容易。本文记录了从零打通的全过程，包括一个关键的坑——WeChat 4.1+ 版本的密钥派生问题。\n技术原理：微信本地数据库是怎么加密的？ 微信 Windows 客户端使用 SQLCipher 加密本地数据库，核心加密参数：\n参数 值 加密算法 AES-256-CBC 页面大小 4096 字节 Salt 位置 每个数据库文件前 16 字节 KDF PBKDF2-HMAC-SHA512 KDF 迭代次数 256,000 HMAC HMAC-SHA512 微信的数据目录通常在 C:\\Users\\\u0026lt;用户名\u0026gt;\\Documents\\xwechat_files\\\u0026lt;微信ID\u0026gt;\\db_storage\\，里面有十几个 .db 文件，每个都用不同的密钥加密（因为每个文件的 salt 不同）。\n关键变化：WeChat 4.0 vs 4.1 这是整个过程中最大的坑，也是网上大多数教程没有提到的：\n版本 内存中存储的内容 提取方式 4.0.x PBKDF2 派生后的原始加密密钥（x'\u0026lt;hex\u0026gt;' 格式） 内存扫描可直接提取，拿来即用 4.1+ 派生前的 passphrase（一段原始密钥材料） 内存扫描只能提取 passphrase，还需 PBKDF2 派生才能得到实际加密密钥 也就是说，如果你按 4.0 时代的教程操作，在 4.1+ 上拿到的\u0026quot;密钥\u0026quot;根本解不开数据库——因为它不是最终密钥，而是密钥的原材料。\n第一步：安装 wechat-cli wechat-cli 是一个 Python 命令行工具，可以读取本地微信的加密数据库，提取聊天记录、联系人等信息。\n1 2 3 4 5 6 7 # 方式一：从 PyPI 安装 pip install wechat-cli # 方式二：从源码安装（我用的方式，因为需要适配 WorkBuddy 环境） git clone https://github.com/pingao123/wechat-cli.git cd wechat-cli pip install -e . ⚠️ 环境隔离提示：如果你用的是 WorkBuddy 等 AI 助手，它可能有自己的 Python 环境。务必确认 wechat-cli 安装到了正确的 Python 环境中，否则后续调用会找不到命令。\n安装后验证：\n1 2 wechat-cli --version # 输出：wechat-cli, version 0.2.4 第二步：尝试初始化（遇到第一个坑） 按照文档，运行 wechat-cli init 应该能自动从微信进程内存中提取加密密钥：\n1 wechat-cli init 结果：0/18 salts matched —— 18 个数据库文件，一个都没解开。\n排查过程 确认微信版本：我的微信是 4.1.10.29（Windows），而 wechat-cli 的内存扫描方式是为 4.0.x 设计的 尝试 wechat-decrypt：这是 wechat-cli 的上游项目，同样使用内存扫描——同样失败 确认不是环境问题：刚开始以为是 WorkBuddy 重装导致的，但反复排查后发现，是微信版本升级后密钥提取方式变了 根因 WeChat 4.1+ 不再在进程内存中缓存派生后的原始加密密钥，只存储派生前的 passphrase。传统的内存扫描方式找到的值无法直接用于 SQLCipher 解密。\n第三步：使用 wx_key 提取 passphrase wx_key 是一个专门针对 WeChat 4.1+ 的密钥提取工具，使用 DLL 注入方式而非内存扫描。\n下载与运行 从 GitHub Releases 下载最新版（我用的 v2.1.8），解压后运行 wx_key.exe。\n⚠️ DLL 注入安全性：wx_key 通过 DLL 注入方式读取微信进程内存。实际测试中，注入后微信功能正常，不会导致崩溃或异常。但仍建议仅在个人学习研究目的下使用。\n操作步骤 确保微信客户端正在运行并已登录 打开 wx_key.exe，它会自动检测微信进程 点击获取密钥按钮 工具会显示一串十六进制字符串——这就是 passphrase（不是最终的加密密钥！） 示例输出（已脱敏）：\na1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2 ⚠️ 关键认知：这串 hex 不是加密密钥本身，而是密钥的\u0026quot;原材料\u0026quot;。每个数据库文件有自己的 salt，需要用 PBKDF2 算法结合 salt 才能派生出真正的加密密钥。\n第四步：PBKDF2 密钥派生（核心步骤） 这是整篇文章最关键的部分——把 passphrase 转换为每个数据库的实际加密密钥。\n派生原理 加密密钥 = PBKDF2-HMAC-SHA512(passphrase, salt, iterations=256000, dklen=32) 其中：\npassphrase：wx_key 提取的十六进制字符串，转为 bytes salt：每个 .db 文件的前 16 字节 iterations：256000 次（微信的固定参数） dklen：32 字节（AES-256 需要） 完整派生脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import hashlib import os import json # ====== 配置区 ====== # 微信数据库目录（根据你的实际路径修改） db_dir = r\u0026#39;C:\\Users\\\u0026lt;用户名\u0026gt;\\Documents\\xwechat_files\\\u0026lt;微信ID\u0026gt;\\db_storage\u0026#39; # wx_key 提取的 passphrase（替换为你的实际值） passphrase_hex = \u0026#39;\u0026lt;从 wx_key 获取\u0026gt;\u0026#39; # ====== 配置区结束 ====== passphrase = bytes.fromhex(passphrase_hex) result = {} for root, dirs, files in os.walk(db_dir): for name in files: # 跳过 WAL 和 SHM 文件 if not name.endswith(\u0026#39;.db\u0026#39;) or name.endswith((\u0026#39;-wal\u0026#39;, \u0026#39;-shm\u0026#39;)): continue path = os.path.join(root, name) size = os.path.getsize(path) # 跳过空文件 if size \u0026lt; 4096: continue # 读取第一页（4096字节），前16字节是 salt with open(path, \u0026#39;rb\u0026#39;) as f: page1 = f.read(4096) salt = page1[:16] # PBKDF2-HMAC-SHA512 派生密钥 enc_key = hashlib.pbkdf2_hmac( \u0026#39;sha512\u0026#39;, passphrase, salt, 256000, dklen=32 ) rel = os.path.relpath(path, db_dir) result[rel] = { \u0026#34;enc_key\u0026#34;: enc_key.hex(), \u0026#34;salt\u0026#34;: salt.hex(), \u0026#34;size_mb\u0026#34;: round(size / 1024 / 1024, 1) } # 写入 wechat-cli 的配置目录 output_dir = os.path.expanduser(\u0026#39;~/.wechat-cli\u0026#39;) os.makedirs(output_dir, exist_ok=True) with open(os.path.join(output_dir, \u0026#39;all_keys.json\u0026#39;), \u0026#39;w\u0026#39;) as f: json.dump(result, f, indent=2) # 同时写入数据库目录配置 config = {\u0026#34;db_dir\u0026#34;: db_dir} with open(os.path.join(output_dir, \u0026#39;config.json\u0026#39;), \u0026#39;w\u0026#39;) as f: json.dump(config, f, indent=2) # 验证结果 print(f\u0026#34;成功派生 {len(result)} 个数据库的加密密钥\u0026#34;) for name, info in result.items(): print(f\u0026#34; {name}: {info[\u0026#39;size_mb\u0026#39;]}MB, key={info[\u0026#39;enc_key\u0026#39;][:16]}...\u0026#34;) 运行脚本后，~/.wechat-cli/all_keys.json 中会保存所有数据库的派生密钥，~/.wechat-cli/config.json 保存数据库路径配置。\n验证成功 我的环境输出（key 已脱敏）：\n成功派生 18 个数据库的加密密钥 MicroMsg.db: 39.2MB, key=xxxx... MSG0.db: 156.7MB, key=xxxx... ... 18/18 全部成功！🎉\n第五步：使用 wechat-cli 读取聊天记录 密钥配置好后，就可以正常使用 wechat-cli 了：\n查看最近会话 1 wechat-cli sessions --format text 查看与某人的聊天记录 1 2 3 4 5 # 按昵称查看 wechat-cli history \u0026#34;联系人A\u0026#34; --limit 20 --format text # 按微信 ID 查看（如果昵称不匹配，用 ID 更准确） wechat-cli history \u0026#34;\u0026lt;联系人微信ID\u0026gt;\u0026#34; --limit 20 --format text 按时间范围查询 1 wechat-cli history \u0026#34;\u0026lt;联系人微信ID\u0026gt;\u0026#34; --start-time \u0026#34;2026-06-05\u0026#34; --end-time \u0026#34;2026-06-06\u0026#34; --format text 搜索消息 1 2 3 4 5 # 全局搜索 wechat-cli search \u0026#34;项目进度\u0026#34; --format text # 在指定聊天中搜索 wechat-cli search \u0026#34;需求\u0026#34; --chat \u0026#34;\u0026lt;聊天名称\u0026gt;\u0026#34; --format text 导出聊天记录 1 2 3 4 5 # 导出为 Markdown wechat-cli export \u0026#34;联系人A\u0026#34; --format markdown # 导出为 HTML wechat-cli export-html \u0026#34;联系人A\u0026#34; 第六步：配置为 WorkBuddy Skill 为了让 AI 助手更方便地调用，我把 wechat-cli 配置成了 WorkBuddy 的 Skill（技能）。\n创建 Skill 文件 在 ~/.workbuddy/skills/wechat-cli/SKILL.md 中定义技能，包含：\n工具说明和命令列表 可执行文件路径 WeChat 4.1+ 的特殊处理流程（wx_key + PBKDF2） 故障排除指南 关键配置 Skill 中最重要的是记录 WeChat 4.1+ 的完整密钥提取流程，确保 AI 助手在密钥失效时能指导用户重新操作：\n运行 wx_key 提取 passphrase 执行 PBKDF2 派生脚本 验证 all_keys.json 生成成功 完整架构图 ┌─────────────────────────────────────────────────┐ │ WorkBuddy AI │ │ (你的 AI 助手) │ └──────────────┬──────────────────────────────────┘ │ 调用 Skill ▼ ┌─────────────────────────────────────────────────┐ │ wechat-cli Skill │ │ ┌───────────────────────────────────────────┐ │ │ │ 命令：sessions / history / search / ... │ │ │ └───────────────────────────────────────────┘ │ └──────────────┬──────────────────────────────────┘ │ 读取密钥 + 解密查询 ▼ ┌─────────────────────────────────────────────────┐ │ ~/.wechat-cli/all_keys.json │ │ (PBKDF2 派生后的 18 个数据库密钥) │ └──────────────┬──────────────────────────────────┘ │ 密钥来源（密钥失效时需重新操作） ▼ ┌─────────────────────────────────────────────────┐ │ wx_key (DLL 注入) │ │ 提取 WeChat 4.1+ passphrase │ └──────────────┬──────────────────────────────────┘ │ 读取微信进程内存 ▼ ┌─────────────────────────────────────────────────┐ │ WeChat 4.1+ 进程内存 │ │ (存储 passphrase 而非派生密钥) │ └─────────────────────────────────────────────────┘ │ passphrase + salt → PBKDF2 → enc_key ▼ ┌─────────────────────────────────────────────────┐ │ 微信本地数据库 (SQLCipher 加密) │ │ MicroMsg.db / MSG0.db / Contact.db / ... │ │ (每个 .db 文件前 16 字节 = salt) │ └─────────────────────────────────────────────────┘ 踩坑完整记录 # 问题 根本原因 解决方法 1 wechat-cli init 返回 0/18 salts matched WeChat 4.1+ 内存中不再缓存派生后的密钥 使用 wx_key DLL 注入提取 passphrase 2 wechat-decrypt 同样失败 同上，所有基于内存扫描的方案都不适用 4.1+ 同上 3 wx_key 提取的值直接验证失败 提取的是 passphrase，不是加密密钥 用 PBKDF2-HMAC-SHA512 派生实际密钥 4 wechat-cli 安装到了系统 Python，WorkBuddy 找不到 WorkBuddy 有自己的 Python 隔离环境 安装到 WorkBuddy 的 Python 路径下 5 wx_key 从 Gitee 下载超时/损坏 Gitee CDN 不稳定 改用 GitHub 镜像下载 6 密钥在微信重启后失效 passphrase 是动态的，每次登录可能改变 重新运行 wx_key + PBKDF2 派生脚本 重点展开：问题 #3 —— passphrase ≠ 加密密钥 这是最容易搞错的一步。wx_key 输出的值看起来就像一个密钥（64 个十六进制字符 = 32 字节），但实际上它是 PBKDF2 的输入参数 passphrase。\n错误做法（❌）：\n1 2 3 # 直接把 wx_key 输出当密钥用 key = bytes.fromhex(\u0026#34;a1b2c3d4e5f6...\u0026#34;) # → SQLCipher 解密失败！ 正确做法（✅）：\n1 2 3 4 5 6 import hashlib passphrase = bytes.fromhex(\u0026#34;a1b2c3d4e5f6...\u0026#34;) # wx_key 提取的值 salt = db_file_first_16_bytes # 从 .db 文件读取 enc_key = hashlib.pbkdf2_hmac(\u0026#39;sha512\u0026#39;, passphrase, salt, 256000, dklen=32) # → 18/18 全部解密成功！ 为什么？ 因为 WeChat 4.1+ 的设计是：每个数据库文件有独立的 salt（文件头前 16 字节），运行时用 PBKDF2(passphrase, salt, 256000) 动态派生加密密钥。这样即使某个数据库的密钥被泄露，其他数据库仍然安全。\n注意事项与风险提示 ⚠️ 使用前请充分了解以下风险：\n违反服务条款 — 读取微信本地数据库可能违反微信服务条款 账号风险 — 微信有权对使用第三方工具的账号进行限制 版本兼容 — 微信更新后工具可能失效，需要等待社区适配 密钥时效 — passphrase 在微信重启或更新后可能改变，需重新提取 杀毒软件 — DLL 注入可能被杀毒软件拦截，需要临时关闭或加白名单 隐私保护 — 发布相关内容时，务必将联系人姓名、微信 ID、passphrase 等敏感信息做脱敏处理 建议：\n仅在个人学习研究目的下使用 不要在处理重要业务的账号上使用 定期备份 ~/.wechat-cli/all_keys.json，避免重复操作 发布博客或文章时，务必对 passphrase、联系人姓名、微信 ID 等做脱敏处理 了解并接受可能的后果 总结 环节 工具 难度 备注 安装 wechat-cli pip ⭐ 注意 Python 环境隔离 提取 passphrase wx_key ⭐⭐ DLL 注入，需要微信运行中 PBKDF2 密钥派生 Python 脚本 ⭐⭐⭐ 核心步骤，4.1+ 必须 配置 WorkBuddy Skill SKILL.md ⭐⭐ 一次配置，长期使用 日常使用 wechat-cli 命令 ⭐ 密钥失效时需重新执行步骤 2~3 核心收获： WeChat 4.1+ 的加密体系从\u0026quot;直接缓存派生密钥\u0026quot;升级为\u0026quot;运行时 PBKDF2 派生\u0026quot;，这意味着传统的内存扫描方案不再适用。理解 passphrase → PBKDF2 → enc_key 的派生链路，是打通微信与 AI 的关键。\n如果你也在尝试将微信数据接入 AI 助手，希望这篇文章能帮你少走弯路。\n本文由 AI 助手辅助整理，所有踩坑经验均为真实记录。发布前已对敏感信息做脱敏处理，实际使用中请务必注意数据安全。\n系列文章（AI 工具链） 本博客「AI 工具链」系列，教你把各类研发工具接入 AI 助手（悟空）：\n让 AI 管理你的代码仓库：Gitea MCP Server 接入指南 让 AI 帮你管代码评审：Gerrit 接入指南 让 AI 管理你的项目：禅道接入指南 让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南 让 AI 读懂你的企业微信：wechat-decrypt 接入指南 在 WorkBuddy 中通过 REST API 操作 Jenkins：原理与实战 在 Jenkins 上搭建基于 Docker 的编译流水线：手把手教程 ","permalink":"https://sikinzen.github.io/posts/howtoconnectwechatandai/","summary":"记录将个人微信聊天记录接入 AI 助手的完整过程，重点解决 WeChat 4.1+ 新版加密密钥派生问题，实现 AI 读取微信聊天记录","title":"让 AI 读懂你的微信聊天记录：wechat-cli + wx_key 接入指南"},{"content":"为什么要自己搭博客？ 在 CSDN 写了多年技术文章，一直想有一个完全属于自己的空间——文章存在自己硬盘里，域名自己定，不需要被平台审核，也没有广告。\n恰巧最近在用 AI 助手（WorkBuddy），于是尝试让它全程帮我搭建。结果出乎意料地顺利，从零到上线只花了一个下午。\n本文记录完整过程，每一步都可直接复现，供有类似需求的同学参考。\n技术选型：为什么选 Hugo + GitHub Pages？ 方案 优点 缺点 Hugo + GitHub Pages 免费、完全可控、Markdown 写作、随时可迁移 需要一点命令行基础 Hexo + Vercel 交互性强、现代感 配置相对复杂 语雀 / 知乎 零门槛、有流量 数据不在自己手里 WordPress 功能全面 维护成本高、需要服务器 最终选 Hugo + GitHub Pages，核心原因是：文章用 Markdown 写，100% 可迁移，平台跑路也不怕。\n第一步：安装 Hugo Hugo 是用 Go 写的静态站点生成器，特点是构建速度极快（几千页毫秒级）。\nWindows 安装（推荐 winget） 1 winget install Hugo.Hugo.Extended ⚠️ 务必安装 Extended 版本，支持 SASS/SCSS，否则 PaperMod 等主题会构建失败。\nmacOS / Linux 安装 1 2 3 4 5 6 7 8 # macOS（用 Homebrew） brew install hugo # Ubuntu / Debian sudo apt install hugo # 或者下载 release 二进制 # https://github.com/gohugoio/hugo/releases 验证安装 1 2 hugo version # 输出：hugo v0.162.1+extended ... ✅ 本文后续 workflow 配置中指定 Hugo 版本为 0.162.1，建议安装相同版本避免兼容问题。\n第二步：创建站点 1 2 3 4 5 # 创建新站点 hugo new site wenling-buyi-blog # 进入目录 cd wenling-buyi-blog 目录结构如下：\nwenling-buyi-blog/ ├── archetypes/ # 文章模板（hugo new 时自动套用） ├── assets/ # 需要处理的前端资源 ├── content/ # 【你的文章放这里】 ├── data/ # 数据文件 ├── layouts/ # 自定义布局 ├── static/ # 图片等静态资源（直接复制到产出目录） ├── themes/ # 主题目录 ├── .gitignore # Git 忽略规则 └── config.toml # 【核心配置文件】 💡 hugo new site 创建的只是骨架，主题和内容需要自己加。\n第三步：配置 .gitignore 在站点根目录创建 .gitignore，避免把构建产物提交到 Git：\n# Hugo 构建产物 /public/ /resources/_gen/ /.hugo_cache/ # 系统文件 .DS_Store Thumbs.db # IDE .vscode/ .idea/ # 主题子模块的临时文件 /themes/*/node_modules/ /themes/*/package-lock.json 第四步：安装 PaperMod 主题 PaperMod 是一款非常流行的 Hugo 主题，简洁、响应式、对中文支持好，GitHub Stars 超过 10k。\n用 Git 子模块方式安装（推荐） 1 2 git init git submodule add https://github.com/adityatelange/hugo-PaperMod themes/PaperMod ✅ 为什么用 submodule？ 主题后续有更新时，只需 git submodule update --remote 即可同步，不会和你自己的修改冲突。\n在 config.toml 中指定主题 1 theme = \u0026#34;PaperMod\u0026#34; 第五步：配置 config.toml（最核心的一步） 直接上我的最终配置（已验证可用），逐项加了注释：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 baseURL = \u0026#39;https://sikinzen.github.io/\u0026#39; languageCode = \u0026#39;zh-CN\u0026#39; locale = \u0026#39;zh-CN\u0026#39; # Hugo v0.158+ 使用，替代 languageCode 的废弃警告 title = \u0026#39;温陵布衣\u0026#39; theme = \u0026#34;PaperMod\u0026#34; # ===== 基础参数 ===== [params] description = \u0026#34;技术探索与实践心得\u0026#34; # ===== 首页 Profile 模式（展示个人信息）===== [params.homeInfoParams] Title = \u0026#34;你好 👋，我是 sikinzen\u0026#34; Content = \u0026#34;\u0026#34;\u0026#34; 📍 厦门思明 🔧 手机方案设计 / AI 设备 / 对俄外贸 💻 技术栈：嵌入式 Linux、Android、AI 应用开发 这里记录技术探索与实践心得，偶尔也聊聊工具与方法论。 \u0026#34;\u0026#34;\u0026#34; # ===== 导航栏 ===== [[params.mainSections]] section = \u0026#34;posts\u0026#34; # ===== 文章目录（右侧 TOC）===== [params.toc] enable = true endLevel = 4 # 最多显示到 #### 四级标题 # ===== 代码高亮 ===== [markup.highlight] anchorLineNos = true lineAnchors = true lineNumbersInTable = true # ===== 分页 ===== [pagination] pagerSize = 10 # ===== 社会化链接（右上角）===== # 在 config.toml 的 [params] 下添加： # [[params.socialIcons]] # name = \u0026#34;github\u0026#34; # url = \u0026#34;https://github.com/sikinzen\u0026#34; 📝 更多 PaperMod 配置选项 参见官方文档：https://github.com/adityatelange/hugo-PaperMod/wiki/Variables\n第六步：配置文章模板（archetypes） hugo new 创建新文章时，会自动从 archetypes/default.md 读取模板。创建一个好用的模板能大幅提升写作体验：\n在 archetypes/default.md 写入：\n1 2 3 4 5 6 7 8 9 10 11 --- title: \u0026#34;{{ replace .Name \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#34; date: {{ .Date }} draft: true description: \u0026#34;\u0026#34; summary: \u0026#34;\u0026#34; categories: [] tags: [] keywords: [] series: [] --- 💡 draft: true 表示文章是草稿，构建时不会发布到线上。draft: false 才正式发布。\n第七步：写第一篇文章 1 2 3 # ⚠️ 必须在博客根目录下运行！ cd G:\\AiStudy\\MyBlog\\Wenling-buyi-blog hugo new posts/hello.md 打开生成的 content/posts/hello.md，编辑内容：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 --- title: \u0026#34;温陵布衣，开张了\u0026#34; date: 2026-06-04T22:00:00+08:00 draft: false description: \u0026#34;从 CSDN 迁移到 Hugo + GitHub Pages，一个属于我自己的技术角落\u0026#34; tags: [\u0026#34;开篇\u0026#34;] categories: [\u0026#34;随笔\u0026#34;] --- 从 CSDN 迁移到 Hugo + GitHub Pages，一个属于我自己的技术角落 🎉 ## 为什么迁移？ CSDN 虽然方便，但广告多、审核严、数据不在自己手里。 用自己的域名、自己的平台，写起来更自在。 ## 接下来 会把之前在 CSDN 上的技术文章逐步迁移过来， 同时也在这里记录新的技术探索。 第八步：本地预览 在发布之前，先本地预览确认效果：\n1 hugo server -D -D 参数表示包含草稿文章（draft: true 的也会显示）。\n然后在浏览器打开 http://localhost:1313 就能实时预览。修改 Markdown 文件后会自动刷新，非常方便。\n按 Ctrl + C 停止本地服务器。\n第九步：创建 About 页面 + 搜索/归档页 9.1 关于页 1 2 # 创建 about 页面 hugo new content/about.md 编辑 content/about.md：\n1 2 3 4 5 6 7 8 9 10 --- title: \u0026#34;关于\u0026#34; description: \u0026#34;关于温陵布衣\u0026#34; --- 这里是「温陵布衣」的技术博客。 温陵是泉州古称，布衣取\u0026#34;布衣之士\u0026#34;之意。 - GitHub：https://github.com/sikinzen - 邮箱：sikinzen@example.com（替换为真实邮箱） 9.2 搜索页（⚠️ 很多人会漏掉这步！） PaperMod 导航栏自带搜索按钮，但需要手动创建对应页面文件，否则点击后 404：\n1 mkdir -p content/search 创建 content/search/_index.md：\n1 2 3 4 5 --- title: \u0026#34;Search\u0026#34; layout: search description: \u0026#34;搜索文章\u0026#34; --- ⚠️ 必须是 _index.md（下划线开头），不能写成 search.md！详见踩坑记录 #10~#12。\n9.3 归档页（同上，不创建会 404） 1 mkdir -p content/archives 创建 content/archives/_index.md：\n1 2 3 4 5 --- title: \u0026#34;Archives\u0026#34; layout: archives description: \u0026#34;文章归档\u0026#34; --- 第十步：配置 GitHub Pages 自动部署（最容易踩坑的一步） 10.1 在 GitHub 创建仓库 打开 https://github.com/new Repository name 必须填：你的用户名.github.io 例如我的用户名是 sikinzen，仓库名就是 sikinzen.github.io 选择 Public 不要勾选 \u0026ldquo;Add a README file\u0026rdquo; 点击 Create repository 10.2 创建 GitHub Actions workflow 在本地博客目录创建文件 .github/workflows/deploy.yml：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 name: Deploy Hugo to GitHub Pages on: push: branches: [\u0026#34;main\u0026#34;, \u0026#34;master\u0026#34;] workflow_dispatch: # 支持手动触发 permissions: contents: read pages: write id-token: write concurrency: group: \u0026#34;pages\u0026#34; cancel-in-progress: false defaults: run: shell: bash jobs: build: runs-on: ubuntu-latest env: HUGO_VERSION: 0.162.1 steps: - name: Checkout uses: actions/checkout@v4 with: submodules: true # ⚠️ 关键：拉取 PaperMod 子模块 fetch-depth: 0 - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: ${{ env.HUGO_VERSION }} extended: true - name: Build with Hugo run: hugo --minify --gc - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ./public deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ⚠️ 注意 branches 要同时包含 \u0026quot;main\u0026quot; 和 \u0026quot;master\u0026quot;，因为不同人的 GitHub 默认分支可能不同。\n10.3 关联远程仓库并推送 1 2 3 4 5 6 # 在博客根目录执行 git add . git commit -m \u0026#34;init: 温陵布衣博客初始化\u0026#34; git branch -M master git remote add origin https://github.com/sikinzen/sikinzen.github.io.git git push -u origin master 把 sikinzen 替换成你自己的 GitHub 用户名。\n第十一步：在 GitHub 开启 Pages 这一步必须手动操作，AI 没法替你点：\n打开 https://github.com/你的用户名/你的用户名.github.io/settings/pages 找到 Source（源） 部分 把选项从 \u0026ldquo;Deploy from a branch\u0026rdquo; 改成 \u0026ldquo;GitHub Actions\u0026rdquo; 点击 Save 保存 设置完之后，GitHub Actions 会自动运行，约 1~2 分钟博客就上线了。\n第十二步：验证部署状态 推送后，访问你的仓库 Actions 标签页，查看部署进度：\n🟡 in progress — 正在构建，等 1~2 分钟 🟢 green checkmark — 部署成功！访问 https://你的用户名.github.io/ 查看 🔴 red X — 构建失败，点进去看 Logs 找原因 常见构建失败原因 错误信息 原因 解决方法 process exited with error: 1 Hugo 版本太旧，与 PaperMod 不兼容 workflow 里指定 hugo-version: '0.162.1' theme not found 没拉取 PaperMod 子模块 checkout 步骤加 submodules: true 推送后没触发 Actions workflow 里只监听 main，但仓库默认分支是 master 在 branches 里加上 \u0026quot;master\u0026quot; 404 页面 GitHub Pages 没开启，或 Source 没选 \u0026ldquo;GitHub Actions\u0026rdquo; 去 Settings → Pages 手动开启 日常写作与发布流程（最重要的一节） 博客搭好之后，以后每次写文章只需要 4 步：\n完整流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # ========== 第 1 步：创建新文章 ========== cd G:\\AiStudy\\MyBlog\\Wenling-buyi-blog hugo new posts/文章标题.md # 注意：必须在博客根目录运行！ # 生成位置：content/posts/文章标题.md # ========== 第 2 步：编辑文章内容 ========== # 用 VS Code 或任何 Markdown 编辑器打开 # content/posts/文章标题.md # # 重要字段说明： # draft: true → 草稿，构建时不发布（本地 hugo server -D 可以看到） # draft: false → 正式发布，git push 后就上线 # description → 文章摘要，显示在文章列表页 # tags → 标签，便于分类检索 # categories → 分类，比 tags 更粗粒度 # ========== 第 3 步：本地预览（可选）========== hugo server -D # 浏览器打开 http://localhost:1313 预览 # 确认效果后按 Ctrl+C 停止 # ========== 第 4 步：发布上线 ========== git add . git commit -m \u0026#34;new post: 文章标题\u0026#34; git push # → 自动触发 GitHub Actions 部署 # → 约 1~2 分钟后访问 https://你的用户名.github.io/ 看到新文章 ✅ Front Matter 字段速查表 每篇文章顶部的 --- 包裹的部分叫做 Front Matter，常见字段：\n字段 说明 示例 title 文章标题（必填） \u0026quot;如何使用 Hugo 搭建博客\u0026quot; date 发布时间（必填） 2026-06-05T13:00:00+08:00 draft 是否为草稿 true（不发布）/ false（发布） description 文章摘要 \u0026quot;本文介绍...\u0026quot; summary 自定义摘要（覆盖 description） \u0026quot;...\u0026quot; tags 标签（可多个） [\u0026quot;Hugo\u0026quot;, \u0026quot;GitHub Pages\u0026quot;] categories 分类（可多个） [\u0026quot;工具\u0026quot;, \u0026quot;教程\u0026quot;] keywords 关键词（SEO 用） [\u0026quot;博客搭建\u0026quot;] series 文章系列 [\u0026quot;Hugo 教程\u0026quot;] \u0026mdash;\u0026mdash;\u0026mdash; \u0026mdash;\u0026mdash; \u0026mdash;\u0026mdash;\u0026mdash; 带附件的文章（Page Bundle 模式） 当文章需要附带图片、PDF、压缩包等附件时，需要使用 Hugo 的 Page Bundle 机制。这是 Hugo 原生支持的特性，与传统的单文件文章并存，无需全局迁移。\n两种文章模式对比 模式 存放方式 适用场景 传统单文件 content/posts/文章名.md 纯文本文章，无附件 Page Bundle（目录） content/posts/文章名/index.md + 附件文件 需要附带图片、PDF、代码包等 两种模式完全兼容，Hugo 会自动识别：\n存在 content/posts/foo.md → 当作单文件文章处理 存在 content/posts/bar/ 目录且含 index.md → 当作 Page Bundle 处理 存量文章无需任何迁移，只有需要加附件时才将对应文章转为目录结构。\n创建 Page Bundle 文章的两种方式 方式一：hugo new 后手动转目录（推荐）\n1 2 3 4 5 6 7 8 9 10 # 第一步：用 hugo new 生成单文件（自动填充 Front Matter） cd G:\\AiStudy\\MyBlog\\Wenling-buyi-blog hugo new posts/文章名.md # 第二步：转成目录结构 mkdir content\\posts\\文章名 move content\\posts\\文章名.md content\\posts\\文章名\\index.md # 第三步：放入附件 copy 你的附件.png content\\posts\\文章名\\ 方式二：直接手动创建目录和 index.md\n1 2 mkdir content\\posts\\文章名 # 然后手动创建 index.md 并填写 Front Matter 💡 方式一更推荐，因为 hugo new 会自动从 archetypes/default.md 读取模板，省去手动填写 Front Matter 的麻烦。\n在文章中引用附件 方式一：正文中直接 Markdown 链接（精准引用）\n适合在文章特定位置引用某个附件：\n1 2 3 4 5 6 7 架构参考下图： ![架构图](architecture.png) 配套代码下载：[sample-code.zip](sample-code.zip) 参考文档：[设计文档.pdf](design-doc.pdf) ⚠️ 路径是相对路径，因为 index.md 和附件在同一个目录下。\n方式二： 短代码（自动罗列全部附件）\n在文章末尾添加这行，Hugo 会自动列出该目录下所有非 Markdown 文件：\n1 2 3 --- 渲染效果包含：\n文件类型图标（📗 PDF、📦 压缩包、🖼️ 图片、📝 Word、📊 Excel、💻 代码等） 文件名和格式标签 文件大小 完整示例 目录结构：\ncontent/posts/how-to-use-openclaw/ ├── index.md # 文章正文 ├── architecture.png # 架构图 ├── sample-code.zip # 示例代码包 └── reference.pdf # 参考文档 index.md 内容：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 --- title: \u0026#34;How To Use OpenClaw\u0026#34; date: 2026-06-14T10:00:00+08:00 draft: false description: \u0026#34;OpenClaw 入门指南\u0026#34; tags: [\u0026#34;AI\u0026#34;, \u0026#34;OpenClaw\u0026#34;] --- ## 架构说明 如下图所示： ![架构图](architecture.png) ## 示例代码 下载示例代码：[sample-code.zip](sample-code.zip) ## 参考资料 [reference.pdf](reference.pdf) --- 自定义附件列表样式（可选） 如果需要对附件列表区域做样式调整，可以在 assets/css/extended/attachments.css 中自定义。PaperMod 主题会自动加载 assets/css/extended/ 目录下的所有 CSS 文件。\n进阶：常用自定义配置 修改主题颜色 PaperMod 支持通过 style 参数自定义主题色，在 config.toml 的 [params] 下添加：\n1 2 3 4 [params] # 亮色模式主题色 [params.style] desktopTheme = \u0026#34;light\u0026#34; # 桌面端默认主题：light / dark / auto 添加头像/作者图片 把头像图片放到 static/images/avatar.jpg 在 config.toml 的 [params.homeInfoParams] 上方添加： 1 2 3 4 5 6 7 8 9 10 [params] # 头像（显示在首页） [params.profileMode] enabled = true title = \u0026#34;你好 👋，我是 sikinzen\u0026#34; subtitle = \u0026#34;技术探索与实践心得\u0026#34; imageUrl = \u0026#34;images/avatar.jpg\u0026#34; imageTitle = \u0026#34;my avatar\u0026#34; imageWidth = 120 imageHeight = 120 添加评论系统（可选） PaperMod 内置支持多个评论系统，以 Giscus（基于 GitHub Discussions，免费）为例：\n先按 https://giscus.app/ 的指引配置好 GitHub Discussions 在 config.toml 添加： 1 2 3 4 5 6 7 8 9 [params] [params.giscus] repo = \u0026#34;sikinzen/sikinzen.github.io\u0026#34; repoId = \u0026#34;你的 repo ID\u0026#34; category = \u0026#34;Announcements\u0026#34; categoryId = \u0026#34;你的 category ID\u0026#34; mapping = \u0026#34;pathname\u0026#34; reactionsEnabled = true theme = \u0026#34;preferred_color_scheme\u0026#34; 从 CSDN 迁移旧文章 如果你之前在 CSDN 有文章想迁移过来：\n方法一：手动复制（适合文章少的情况） 在 CSDN 打开旧文章，复制正文 Markdown（CSDN 编辑器支持导出 Markdown） hugo new posts/旧文章标题.md 粘贴内容，补充 front matter git add . \u0026amp;\u0026amp; git commit \u0026amp;\u0026amp; git push 方法二：批量转换（适合文章多的情况） CSDN 文章导出后通常是 HTML 或 Markdown，可以用 pandoc 做格式转换：\n1 2 3 4 5 # 安装 pandoc winget install pandoc # HTML 转 Markdown pandoc old-article.html -t markdown -o content/posts/old-article.md 💡 转换后务必人工检查一遍，特别是代码块和图片链接。\n图片处理 CSDN 的图片在 https://img-blog.csdn.net/ 等地址，建议下载到本地：\nstatic/images/ ├── 2024-01-01-article1/ │ ├── img1.png │ └── img2.jpg 然后在 Markdown 里引用：\n1 ![图片说明](/images/2024-01-01-article1/img1.png) 踩坑完整记录 以下是我搭建过程中真实遇到的所有坑，逐一记录解决方法：\n# 问题 根本原因 解决方法 1 hugo new 执行后找不到生成的文件 当前目录不在博客根目录 先 cd 到博客根目录再执行 2 push 后博客 404 GitHub Pages 默认没开启 Settings → Pages → Source 选 \u0026ldquo;GitHub Actions\u0026rdquo; 3 Build 失败（exit code 1） Hugo 版本太旧，与 PaperMod 不兼容 workflow 指定 hugo-version: '0.162.1' 4 触发不了 Actions workflow 只监听 main，但仓库默认分支是 master branches 加上 \u0026quot;master\u0026quot; 5 主题没生效 / 构建报错找不到主题 checkout 没拉取子模块 加 submodules: true 6 fatal: 'themes/PaperMod' already exists and is not a valid git repo 之前有残留目录但不是合法子模块 rm -rf themes/PaperMod 后重新 git submodule add 7 *** Please tell me who you are. Git 未配置用户信息 git config user.email \u0026quot;xxx\u0026quot; + git config user.name \u0026quot;xxx\u0026quot; 8 本地 hugo server 正常，但 GitHub Actions 构建失败 本地 Hugo 版本与 workflow 指定版本不一致 统一版本号 9 languageCode 废弃警告 Hugo v0.158+ 改用 locale 同时保留 languageCode（兼容旧版）和 locale（新版） 10 🔍 导航栏\u0026quot;搜索\u0026quot;按钮点击后 404 PaperMod 需要搜索页文件，但未创建 创建 content/search/_index.md（注意是 _index.md，不是 .md） 11 📂 导航栏\u0026quot;归档\u0026quot;按钮点击后 404 PaperMod 需要归档页文件，但未创建 创建 content/archives/_index.md（同上） 12 创建了 search.md / archives.md 但仍然 404 用了普通 .md 文件格式，Hugo 把它当单页面处理，找不到 PaperMod 的内置模板 改用 _index.md 格式（section 首页），并放在对应的子目录中 🔥 重点展开：搜索和归档页 404 问题（问题 #10~#12） 这个问题非常容易遇到，因为 PaperMod 主题的导航栏默认就有「搜索」和「归档」两个按钮，但不会自动创建对应页面。\n现象 博客上线后，点击右上角：\n🔍 搜索 → 跳转到 /search/ → 显示 404 📂 归档 → 跳转到 /archives/ → 显示 404 错误做法（第一次尝试 ❌） 1 2 3 4 # ❌ 这样写不行！ echo \u0026#39;---\\nlayout: search\\n---\u0026#39; \u0026gt; content/search.md echo \u0026#39;---\\nlayout: archives\\n---\u0026#39; \u0026gt; content/archives.md # 结果：部署成功但访问仍然是 404 原因：普通 .md 文件在 Hugo 中被当作独立单页面处理，而 PaperMod 的搜索/归档功能需要的是 Section 首页——即该目录下的 _index.md。\n正确做法 ✅ 1 2 3 4 # 第一步：创建子目录 mkdir -p content/search content/archives # 第二步：创建 _index.md（注意下划线开头！） 创建 content/search/_index.md：\n1 2 3 4 5 --- title: \u0026#34;Search\u0026#34; layout: search description: \u0026#34;搜索文章\u0026#34; --- 创建 content/archives/_index.md：\n1 2 3 4 5 --- title: \u0026#34;Archives\u0026#34; layout: archives description: \u0026#34;文章归档\u0026#34; --- 💡 关键知识点：Hugo 的两种页面类型\n类型 文件路径 用途 示例 普通页面 content/about.md 独立的单篇文章或页面 关于页、联系页 Section 首页 content/posts/_index.md 该 section 的索引/列表页 文章列表、搜索、归档 搜索和归档都属于后者，必须用 _index.md 格式，否则 Hugo 找不到对应的 layout 模板。\n提交并发布 1 2 3 4 5 cd G:\\AiStudy\\MyBlog\\Wenling-buyi-blog git add content/search/ content/archives/ git commit -m \u0026#34;fix: 添加搜索和归档页，修复导航 404\u0026#34; git push # → 1~2 分钟后生效 ✅ 其他值得注意的问题 推送后 Actions 没有自动触发 如果先推送代码、再去 Settings → Pages 开启 GitHub Actions，之前的推送不会触发 Actions。需要再推一次（哪怕是空提交）来触发首次部署：\n1 2 git commit --allow-empty -m \u0026#34;trigger: 启动 GitHub Pages 部署\u0026#34; git push 博客目录迁移后 Git 远程仓库丢失 如果用 robocopy 或手动复制方式迁移了博客目录，.git 目录可能不完整。迁移后务必验证：\n1 2 3 4 cd 新目录 git status # 应显示 On branch master, working tree clean git remote -v # 应显示 origin → https://github.com/xxx/xxx.git git log --oneline -5 # 应看到完整的提交历史 如果 git remote -v 无输出，需要重新关联：\n1 git remote add origin https://github.com/sikinzen/sikinzen.github.io.git 后续完善记录 博客上线后，又陆续做了不少优化。以下按时间线记录，方便后续追溯。\n一、Bug 修复 1. languageCode 弃用警告\nHugo v0.158+ 中 languageCode 被标记为废弃，改用 locale。config.toml 中已删除 languageCode 字段，仅保留 locale = 'zh-CN'。\n2. 搜索页 \u0026amp; 归档页 404\n根因：content/search/_index.md 和 content/archives/_index.md 中 Front Matter 的 --- 结束符缺失，导致 Hugo 无法正确解析，构建后这两个页面为空白/404。补全结束符后恢复正常。\n二、功能增强 1. 显示文章修改时间（ShowLastMod）\n三步实现：\n1 2 3 4 5 # config.toml enableGitInfo = true # 让 Hugo 从 git log 自动提取每个文件的最后提交时间 [params] ShowLastMod = true # 在文章页显示\u0026#34;修改: YYYY年M月D日\u0026#34; 创建自定义模板 layouts/partials/post_meta.html，覆盖 PaperMod 主题默认的元信息渲染逻辑，新增 lastmod 显示行：\n1 2 3 4 5 {{- if and site.Params.ShowLastMod (not .Lastmod.IsZero) (ne .Lastmod .Date) -}} \u0026lt;span title=\u0026#34;最后修改: {{ .Lastmod }}\u0026#34;\u0026gt; 修改: {{ .Lastmod | time.Format \u0026#34;:date_long\u0026#34; }} \u0026lt;/span\u0026gt; {{- end }} ⚠️ 前提：GitHub Actions workflow 中 fetch-depth: 0 确保拉取完整 git 历史，否则 .GitInfo 只能获取最近一次提交时间。\n2. 文章修改记录链接（editPost）\n在每篇文章底部添加\u0026quot;📝 修改记录\u0026quot;链接，点击跳转到 GitHub 上该文件的完整 commit 历史，每一次修改的时间、作者、commit message 一目了然：\n1 2 3 4 5 # config.toml [params] 下新增 [params.editPost] URL = \u0026#34;https://github.com/sikinzen/sikinzen.github.io/commits/master/content\u0026#34; Text = \u0026#34;📝 修改记录\u0026#34; appendFilePath = true # 自动拼接当前文件相对路径 生成的实际链接示例：https://github.com/sikinzen/sikinzen.github.io/commits/master/content/posts/HowToBuildBlog.md\n3. 分类页面与导航栏\n创建 content/categories/_index.md，并在 config.toml 导航菜单中添加「🏷️ 分类」入口：\n1 2 3 4 5 [[menu.main]] identifier = \u0026#34;categories\u0026#34; name = \u0026#34;🏷️ 分类\u0026#34; url = \u0026#34;/categories/\u0026#34; weight = 15 4. 评论系统（Giscus）\n基于 GitHub Discussions 的免费评论系统，无需数据库、无需后端，评论者用 GitHub 账号登录即可留言，且自动区分仓库主人（Owner）与普通访客。\n实现方式：PaperMod 主题的 comments.html 是空占位符，通过模板覆盖方式注入 Giscus 脚本。\n第一步：创建 layouts/partials/comments.html：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 \u0026lt;!-- Giscus 评论系统 --\u0026gt; \u0026lt;div class=\u0026#34;giscus\u0026#34;\u0026gt; \u0026lt;script src=\u0026#34;https://giscus.app/client.js\u0026#34; data-repo=\u0026#34;sikinzen/sikinzen.github.io\u0026#34; data-repo-id=\u0026#34;R_kgDOSxL0IQ\u0026#34; data-category=\u0026#34;General\u0026#34; data-category-id=\u0026#34;DIC_kwDOSxL0Ic4C-0wi\u0026#34; data-mapping=\u0026#34;pathname\u0026#34; data-strict=\u0026#34;0\u0026#34; data-reactions-enabled=\u0026#34;1\u0026#34; data-emit-metadata=\u0026#34;0\u0026#34; data-input-position=\u0026#34;bottom\u0026#34; data-theme=\u0026#34;preferred_color_scheme\u0026#34; data-lang=\u0026#34;zh-CN\u0026#34; data-loading=\u0026#34;lazy\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; async\u0026gt; \u0026lt;/script\u0026gt; \u0026lt;/div\u0026gt; 第二步：在 config.toml 中启用评论：\n1 2 [params] comments = true 关键参数说明：\n参数 值 作用 data-repo sikinzen/sikinzen.github.io 绑定的 GitHub 仓库 data-mapping pathname 每篇文章按 URL 路径映射到独立 Discussion 线程 data-theme preferred_color_scheme 自动跟随系统/博客深浅色模式 data-lang zh-CN 评论区界面语言 前置条件（需在 GitHub 上完成）：\n仓库 Settings → Features → 勾选 Discussions 安装 giscus GitHub App 到该仓库 访问 giscus.app 获取 data-category-id 💡 Giscus 自动标记仓库 Owner（sikinzen）为 👑 Owner，普通 GitHub 用户仅显示用户名，天然区分主人与访客，无需额外开发。\n三、内容建设 博客上线后陆续发布了以下文章：\n文件 内容 grill-me-devs-popularity.md Grill Me 深度解析：20个词的AI技能凭什么火遍开发者圈 HowToUseWorkBuddy1st.md 我重度使用 WorkBuddy 后，总结出 6 条真实经验 HowToUseWorkBuddy2nd.md WorkBuddy 从入门到精通——10个上手技巧 HowToUseVPN.md 科学上网配置指南：从服务选购到多端部署 HowToConnectZentaoAndAI.md 禅道接入 AI 实操指南 HowToConnectGerritAndAI.md Gerrit 接入 AI 实操指南 HowToConnectGiteaAndAI.md Gitea 接入 AI 实操指南 HowToConnectWechatAndAI.md 微信本地数据查询接入 AI HowToConnectWeComAndAI.md 企业微信本地数据查询接入 AI 四、WorkBuddy 写博客的积分优化经验 用 WorkBuddy 写博客时，发现单篇文章可能消耗上百积分，分析根因后总结出三板斧：\n策略 效果 原理 粘贴内容替代链接 省 60~80% 微信链接有反爬机制，AI 需要多轮 WebFetch + 搜索重试才能获取内容，直接粘贴免除所有抓取开销 新博客开新会话 省 30~50% 上下文会随对话轮次膨胀，旧会话中每次响应都携带大量历史 token 精简指令不加验证 省 10~20% 去掉\u0026quot;检查格式\u0026quot;\u0026ldquo;预览效果\u0026quot;等非必要步骤，直接生成并交付 💡 最佳实践：开新会话 → 粘贴文章原始内容 → 一句指令\u0026quot;按博客规范整理成 md 文件\u0026rdquo;→ 完成。整个过程通常控制在 20 积分以内。\n总结 环节 工具 难度 预计耗时 安装 Hugo winget / brew ⭐ 5 分钟 创建站点 + 安装主题 hugo + git ⭐ 10 分钟 配置 config.toml 文本编辑器 ⭐⭐ 15 分钟 写第一篇文章 Markdown ⭐ 随意 创建搜索/归档页（PaperMod 必需） Markdown + mkdir ⭐ 5 分钟（容易漏！） 配置 GitHub Actions YAML + GitHub Settings ⭐⭐⭐ 30~60 分钟（主要是踩坑） 日常写文章发布 hugo new + git push ⭐ 3 分钟 最花时间的其实是 GitHub Actions 的配置和调试，其他环节半小时就能搞定。\n但一旦搭好，以后写文章的体验是非常流畅的：用 Markdown 写 → git push → 自动上线，没有广告、没有审核、文章永远在自己硬盘里。\n如果你也在考虑搭建个人博客，Hugo + GitHub Pages 是目前性价比最高的方案，没有之一。\n欢迎来我的博客逛逛 👉 https://sikinzen.github.io/\n本文由 AI 助手（WorkBuddy）辅助整理，但所有踩坑经验均为真实记录。\n","permalink":"https://sikinzen.github.io/posts/howtobuildblog/","summary":"用 WorkBuddy AI 助手，从零搭建 Hugo + PaperMod + GitHub Pages 博客，记录完整过程和踩坑经验","title":"如何使用 AI 搭建自己的博客"},{"content":"👤 关于我 我是 sikinzen（Alex），坐标 厦门思明，偶尔出没于深圳和遵义。\n职业 手机方案设计公司的软件中心负责人，业务涵盖：\n手机方案设计 AI 设备开发 对俄电子产品外贸 技术兴趣 嵌入式 Linux / Android 系统开发 AI 应用与工具链落地 软件工程方法论 开源技术与工具 这个博客 用 Hugo 搭建在 GitHub Pages 上，主题是 PaperMod。\n取名\u0026quot;温陵布衣\u0026quot;——温陵是泉州古称（闽南一隅），布衣即平民书生。意思就是：一个在闽南做技术的普通人，记录点东西。\n联系方式 📌 GitHub: sikinzen 最后更新：2026-06-04\n","permalink":"https://sikinzen.github.io/about/","summary":"sikinzen 的个人技术博客","title":"关于"},{"content":"为什么自建博客？ 之前在 CSDN 上写了不少技术文章，但总感觉不太自在：\n平台绑定 — 内容在别人手上，哪天改规则了只能认 广告泛滥 — 打开自己写的文章，满屏广告 编辑体验差 — Markdown 支持不完善，排版费劲 所以决定迁移到 Hugo + GitHub Pages：\n对比项 CSDN Hugo + GitHub Pages 数据归属 平台 自己（本地 + Git） 广告 满屏 零 编辑 在线编辑器 本地 Markdown + Git 定制 有限 完全自由 费用 免费（用流量换） 免费 关于\u0026quot;温陵布衣\u0026quot; 温陵是泉州的古称，离我所在的厦门不过百里；布衣取\u0026quot;布衣之士\u0026quot;，意指一介书生。\n名字的寓意很简单：身处闽南，以技术为业的一介普通人。不装大牛，不追热点，踏踏实实写点东西。\n技术栈 框架：Hugo — 极速静态站生成器 主题：PaperMod — 简洁优雅 部署：GitHub Pages + GitHub Actions 自动构建 写作：Markdown + Git 版本管理 接下来会写什么？ 大概这些方向：\n嵌入式 Linux / Android 开发实践 AI 工具在工作中的落地应用 技术方案选型与分析 偶尔聊聊效率工具和方法论 不追求更新频率，有干货才写。\n欢迎通过 GitHub 联系交流！\n","permalink":"https://sikinzen.github.io/posts/hello/","summary":"从 CSDN 迁移到 Hugo + GitHub Pages，一个属于我自己的技术角落","title":"温陵布衣，开张了"}]