diff --git a/content/posts/2026/2026-06-25-我的Hugo博客部署优化从7分钟到3分钟的秘密/image.png b/content/posts/2026/2026-06-25-我的Hugo博客部署优化从7分钟到3分钟的秘密/image.png new file mode 100644 index 00000000..5231cafb Binary files /dev/null and b/content/posts/2026/2026-06-25-我的Hugo博客部署优化从7分钟到3分钟的秘密/image.png differ diff --git a/content/posts/2026/2026-06-25-我的Hugo博客部署优化从7分钟到3分钟的秘密/index.md b/content/posts/2026/2026-06-25-我的Hugo博客部署优化从7分钟到3分钟的秘密/index.md new file mode 100644 index 00000000..67ac68f7 --- /dev/null +++ b/content/posts/2026/2026-06-25-我的Hugo博客部署优化从7分钟到3分钟的秘密/index.md @@ -0,0 +1,262 @@ +--- +title: "聊聊我博客部署的优化:从 her-cat/upyun-deployer 换到官方 upx" +date: 2026-06-25T19:54:00+08:00 +tags: ["Hugo", "GitHub Actions", "又拍云", "部署"] +categories: ["折腾"] +draft: false +--- + +端午假期,我把用了挺久的 `her-cat/upyun-deployer` 又拍云自动部署脚本换成了官方出品的 `upx`。 + +https://help.upyun.com/knowledge-base/developer_tools/#upx-efbc88e591bde4bba4e8a18ce5b7a5e585b7efbc89 + +![upx](/image.png) + +这个工具是我在逛官方文档时偶然发现的。之前一直用 GitHub 开源作者 hercat 的 `her-cat/upyun-deployer`,稳定性确实不错,跑了一年多都没出什么问题。不过现在 Hugo 博客的文章越来越多,部署时间也越来越长。 + +GitHub Actions 对免费用户有每月 2000 分钟的运行时长限制,有时候遇到网络波动,部署就会卡很久,甚至直接超时失败。 + +这篇文章简单记录一下迁移过程,顺便对比分析两款工具的实现差异,希望能给同样在用 Hugo + 又拍云的朋友一些参考。 + +## 先聊聊 her-cat/upyun-deployer 的实现 + +在说 upx 之前,我让ai 查了查 `her-cat/upyun-deployer` 的源码,看看它是怎么工作的。作者是 her-cat,这个项目帮了很多人,包括我之前也一直在用,非常感谢作者的付出。 + +它的核心逻辑在 `main.go` 里,大概是这样的: + +```go +func (d *UpYunDeployer) UploadFiles() { + // 第一步:获取远程所有文件列表 + files, dirs := d.GetAllRemoteFilesByPath(publishDir) + + // 第二步:遍历本地文件 + err := filepath.Walk(d.localDir, func(filename string, file os.FileInfo, err error) error { + // ... + // 第三步:对每个文件,先获取远程文件信息 + remoteFileInfo, err := d.getFileInfo(relativeFilename) + + // 第四步:判断是否需要上传 + if upyun.IsNotExist(err) { + d.uploadFile(putObjectConfig, false) + return + } + + // 第五步:MD5 对比,如果相同就跳过 + if remoteFileInfo.MD5 == fmt.Sprintf("%x", md5.Sum(data)) { + fmt.Printf("[%s] cached!\n", relativeFilename) + return + } + + // 第六步:不同的话,先删除再上传 + err = d.deleteFile(relativeFilename, true) + d.uploadFile(putObjectConfig, true) + return nil + }) + + // 第七步:全量 CDN 刷新 + d.up.Purge(urls) + + // 第八步:删除远程多余的文件和目录 + d.deleteFiles(files) + d.deleteDirs(dirs) +} +``` + +整体思路其实很清晰: + +1. 先把远程所有文件列表拉下来 +2. 遍历本地每个文件,和远程对比 MD5 +3. 不一样就先删再上传 +4. 最后全量刷新 CDN,再删掉远程多余的文件 + +这个思路很不错,就是在实际使用中,有几个地方会比较耗时: + +**第一个是每个文件都要单独调 API。** + +上传一个文件之前,先要调 `GetInfo` 检查远程是否存在;如果存在且 MD5 不同,还要先调 `Delete` 删除,再调 `Put` 上传。一个文件搞不好就要发 2-3 次请求,文件多了自然就慢了。 + +**第二个是获取远程文件列表的过程。** + +`GetAllRemoteFiles` 是递归遍历所有目录的,虽然用了 goroutine 并发请求,但文件越多,请求次数就越多,这块也会花不少时间。 + +**第三个是全量 CDN 刷新。** + +每次部署完都会把所有上传过的文件都 Purge 一遍,虽然又拍云的 Purge 是异步的,但全量刷新既浪费配额,也可能导致用户访问的时候缓存命中率下降。 + +## upx 是什么 + +upx 是又拍云官方出的命令行工具,全称是 UpYun Uploader(我猜的)。 + +> UPX 是专为开发者设计的,基于命令行的云存储管理工具。它可以实现文件上下传、增量文件同步、目录创建删除、文件删除等。 + +项目地址:[https://github.com/upyun/upx](https://github.com/upyun/upx) + +我最看重的就是它的 `sync` 命令,类似于Linux的 rsync,支持增量同步。而且因为是官方维护的,对 API 的利用应该会更高效一些。 + +## 我的部署方案 + +下面说说我现在的部署配置,大家可以参考一下。 + +### GitHub Actions 配置 + +我把构建和部署拆成了两个 job,这样构建产物可以复用。 + +```yaml +name: Deploy to Production + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Build Hugo site + run: hugo --gc --minify + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: public + path: ./public + + deploy-upyun: + runs-on: ubuntu-latest + needs: build + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: public + path: ./public + + - name: Deploy to UpYun + env: + UPYUN_SERVICE: ${{ secrets.UPYUN_BUCKET }} + UPYUN_OPERATOR: ${{ secrets.UPYUN_OPERATOR }} + UPYUN_PASSWORD: ${{ secrets.UPYUN_PASSWORD }} + run: | + chmod +x scripts/deploy_upyun.sh + ./scripts/deploy_upyun.sh ./public / 10 +``` + +### 部署脚本 + +然后是具体的部署脚本,放在 `scripts/deploy_upyun.sh`: + +```bash +#!/bin/bash +# UpYun 部署脚本 - 使用 upx 官方工具 + +set -e + +LOCAL_DIR="${1:-./public}" +REMOTE_DIR="${2:-/}" +THREADS="${3:-10}" + +# 又拍云 upx sync 的线程数限制在 1-10 之间 +if [ "$THREADS" -lt 1 ]; then + THREADS=1 +elif [ "$THREADS" -gt 10 ]; then + THREADS=10 +fi + +SERVICE_NAME="${UPYUN_SERVICE:-}" +OPERATOR="${UPYUN_OPERATOR:-}" +PASSWORD="${UPYUN_PASSWORD:-}" + +if [ -z "$SERVICE_NAME" ] || [ -z "$OPERATOR" ] || [ -z "$PASSWORD" ]; then + echo "❌ 错误:缺少必要的环境变量" + exit 1 +fi + +echo "🚀 UpYun 部署开始" + +# 下载又拍云 upx +UPX_VERSION="0.4.9" +UPX_URL="https://collection.b0.upaiyun.com/softwares/upx/upx_${UPX_VERSION}_linux_amd64.tar.gz" +UPX_BIN="/tmp/upyun-upx" + +if [ ! -x "$UPX_BIN" ]; then + echo "📥 下载又拍云 upx v${UPX_VERSION}..." + curl -sL "$UPX_URL" -o /tmp/upx.tar.gz + tar -xzf /tmp/upx.tar.gz -C /tmp + mv /tmp/upx "$UPX_BIN" + chmod +x "$UPX_BIN" + echo "✅ upx 安装完成" +fi + +# 登录并同步 +"$UPX_BIN" login "$SERVICE_NAME" "$OPERATOR" "$PASSWORD" +"$UPX_BIN" sync "$LOCAL_DIR" "$REMOTE_DIR" \ + -w "$THREADS" \ + --delete \ + --strong + +echo "✅ UpYun 部署完成!" +``` + +脚本很简单,就是下载 upx,登录,然后 sync 一下。 + +## upx 为什么更快? + +我自己用下来,upx 确实比 `her-cat/upyun-deployer` 快不少,我个人认为主要有这几个原因: + +**第一是官方实现,对 API 的利用更高效。** + +毕竟是官方自己的工具,很多操作应该是批量处理的,而不是每个文件单独调一次 API。这一点从速度上就能明显感觉出来。 + +**第二是 sync 命令做了专门的优化。** + +`upx sync` 作为核心功能,内部的文件对比、差异计算应该都是经过优化的。不像社区项目,作者可能是业余时间维护,精力有限。 + +**第三是线程控制更稳定。** + +upx 的 `-w` 参数可以控制并发线程数,而且内部应该有完整的错误重试和限流机制,不容易触发又拍云的 API 频率限制。 + +不过有一点要注意,又拍云的 upx sync 线程数上限是 10,别开太大了,开了也没用。 + +## 一些小技巧 + +### build hash 跳过重复部署 + +有时候我们可能只是改了一下 README,或者 CI 配置变了,但 Hugo 构建的产物其实是一样的。这时候可以用 build hash 来判断是否需要重新部署。 + +```yaml +- name: Generate build hash + id: hash + run: echo "build_hash=$(find ./public -type f -exec md5sum {} \; | md5sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT + +- name: Cache build + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-deploy-upyun-${{ steps.hash.outputs.build_hash }} + path: ./public +``` + +如果缓存命中了,说明构建产物没变化,就可以跳过部署步骤,省不少时间。 + +### 注意和系统 upx 重名 + +Linux 系统里可能已经装了另一个 `upx`,就是那个可执行文件压缩工具。为了避免冲突,我在脚本里把下载的又拍云 upx 改名叫 `upyun-upx`,路径放在 `/tmp` 下面,这样就不会搞混了。 + +## 一些注意事项 + +1. **敏感文件别传上去了**:部署前确认一下 `.git`、`.env` 这些东西不在 public 目录里,不然传到 CDN 上就尴尬了。 + +2. **权限尽量给小一点**:去又拍云后台创建一个专门的操作员,只给上传权限就行,别用主账户密码。安全第一。 + +3. **CDN 刷新按需来**:upx sync 上传的文件会自动更新,一般不需要手动 Purge。除非你改了文件名没变内容,那可能需要手动刷新一下。 + +4. **网络问题**:GitHub Actions 的服务器在海外,部署到又拍云国内节点可能会慢一些。我自己用下来还能接受,如果觉得慢的话,可以考虑用国内的 CI 服务。 + +**相关链接:** +- upx 开源项目:[https://github.com/upyun/upx](https://github.com/upyun/upx) +- 又拍云官方文档:[https://help.upyun.com/](https://help.upyun.com/) \ No newline at end of file