blog-assets 桶创建与配置方案
1. 目标
在现有 MinIO 服务上新增一个 blog-assets 桶,用于存放博客/知识库的非图片附件(trace 文件、zip 包、PDF 等)。
1.1 核心动机
当前 fetch-feishu skill 爬取飞书文档时:
- 图片:可以上传到
minio.898311.xyz/upload/api/upload(只接受图片格式) - 非图片附件(.pftrace、.zip、.tar.gz、.pdf 等):无法上传,只能存本地
attachments/目录
实际案例(触发本方案的场景):
附件: 3 个已下载到本地
├─ attachments/perfetto_1779783908.pftrace (1.7MB, 原始分析 trace)
├─ attachments/perfetto_trace_reproduce.pftrace (427MB, 开关复现 trace)
└─ attachments/analysis_skill.zip (20KB, 分析 skill 包)
图床服务只允许图片格式上传,trace/zip 文件无法上传到 blogfile 桶,
所以附件保存在本地 attachments/ 子目录并在文档中用相对路径引用
目标:让 fetch-feishu skill 能把这些附件上传到 blog-assets 桶,生成公网可访问的 URL,替代本地相对路径引用。
1.2 核心要求
- 匿名可下载:任何人通过 URL 直接访问,无需凭证
- 上传需认证:复用现有
uploadrw0511a账号写入 - 内外网统一:通过现有 frpc 隧道,公网自动可用
- skill 可调用:提供简单的
mc cp命令即可完成上传,不依赖图床 API
2. 前置条件
执行前确认以下条件满足:
| 条件 | 确认方式 |
|---|---|
| MinIO 服务运行中 | systemctl status minio 或 mc ls local |
mc 已安装且 local alias 可用 | mc ls local 返回桶列表 |
uploadrw0511a 账号存在 | mc admin user list local 中可见 |
| 策略文件位置已知 | 仓库内 main/blog/devops/misc/minio-upload-rw-policy.json |
| 公网 MinIO API 入口已确认 | 使用 http://tx2.898311.xyz:9010,不要使用 minio.898311.xyz |
| 现有大文件 MinIO 工作流已稳定 | 参考 minio_migration_plan.md、public_minio_remote_clone_plan.md、remote_bootstrap_validation.md |
2.1 本方案复用的既有能力
blog-assets 不是单独新造一套对象存储链路,而是复用当前已经验证通过的 MinIO 基础设施和运维规范:
- 公网 API 入口已经固定为
http://tx2.898311.xyz:9010 - 远程访问主模型已经固定为:
mc+ 受限账号 + 环境变量自动配置 alias uploadrw0511a已经是现有上传/恢复链路使用的普通账号模型mc cp+mc stat已经是仓库大文件上传流程的既有约束
这里复用的是:
- MinIO 基础设施
- 认证与连接方式
- 运维命令与校验方式
这里不复用的是:
generalandroid的业务职责定义generalandroid的对象归属边界- “超大附件回退到
generalandroid” 这类旧思路
因此本方案的重点不是证明“能不能接入 MinIO”,而是补齐 blog-assets 的目录规范、权限边界、缓存策略、生命周期和验收闭环。
2.2 为什么要单独扩展 blog-assets
当前 GitLab 仓库和备份体系已经承接了大量历史大文件,继续把飞书附件、博客 trace、zip、pdf 直接放入仓库,会继续放大:
- 仓库工作区体积
.git历史对象体积- GitLab 备份体积
- clone / pull 的传输压力
因此 blog-assets 的定位应该明确为:
- 用于博客 / 知识库附件的公开分发桶
- 尽量让文档附件走对象存储,而不是继续进 Git / GitLab 备份链路
- 与
generalandroid完全职责分离:generalandroid继续服务仓库大文件迁移与恢复,blog-assets负责博客 / 文档附件分发 - 即使是超大附件,只要语义上属于博客 / 知识库附件,也仍然进入
blog-assets,不回退到generalandroid - 判定上传目标只看内容归属,不看文件大小
3. 执行步骤
步骤 1:创建桶
mc mb local/blog-assets预期输出:
Bucket created successfully `local/blog-assets`.
验证:
mc ls local | grep blog-assets步骤 2:设置匿名下载权限
mc anonymous set download local/blog-assets预期输出:
Access permission for `local/blog-assets` is set to `download`
验证(用 curl 测试匿名访问能力):
# 先上传一个测试文件
echo "blog-assets-public-test" | mc pipe local/blog-assets/test/hello.txt
# 用 curl 匿名下载(不带任何认证头)
curl -s http://127.0.0.1:9000/blog-assets/test/hello.txt预期返回:
blog-assets-public-test
验证公网访问(如果当前机器可以访问公网入口):
curl -s http://tx2.898311.xyz:9010/blog-assets/test/hello.txt预期返回同上。
清理测试文件:
mc rm local/blog-assets/test/hello.txt步骤 3:更新 uploadrw0511a 策略文件
编辑策略文件,将 blog-assets 加入桶级和对象级权限列表。
策略文件位置(仓库内):
main/blog/devops/misc/minio-upload-rw-policy.json
更新后的完整内容:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads"
],
"Resource": [
"arn:aws:s3:::generalandroid",
"arn:aws:s3:::blogfile",
"arn:aws:s3:::upload",
"arn:aws:s3:::tmp-image",
"arn:aws:s3:::blog-assets"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": [
"arn:aws:s3:::generalandroid/*",
"arn:aws:s3:::blogfile/*",
"arn:aws:s3:::upload/*",
"arn:aws:s3:::tmp-image/*",
"arn:aws:s3:::blog-assets/*"
]
}
]
}步骤 4:应用策略到 MinIO
mc admin policy create local upload-rw-policy main/blog/devops/misc/minio-upload-rw-policy.json如果策略已存在会自动覆盖(create = create or replace)。
预期输出:
Created policy `upload-rw-policy` successfully.
确认策略已关联到用户:
mc admin policy entities local --user uploadrw0511a如果输出中没有 upload-rw-policy,则手动关联:
mc admin policy attach local upload-rw-policy --user uploadrw0511a步骤 5:验证 uploadrw0511a 对新桶的读写能力
用 uploadrw alias 验证(如果本机已有 uploadrw alias):
# 列桶
mc ls uploadrw/blog-assets
# 上传
echo "uploadrw-write-test" | mc pipe uploadrw/blog-assets/test/write-check.txt
# 读取
mc cat uploadrw/blog-assets/test/write-check.txt
# 删除
mc rm uploadrw/blog-assets/test/write-check.txt如果本机没有 uploadrw alias,可以临时创建:
mc alias set uploadrw http://tx2.898311.xyz:9010 uploadrw0511a <SECRET_KEY>步骤 6:验证权限边界未被放大
确认 uploadrw0511a 对未授权桶仍然拒绝:
mc ls uploadrw/blogimg预期输出:
ERROR ... Access Denied.
步骤 7:验证正式前缀上传链路
除了测试目录,还要至少验证一次正式前缀写入,确保后续 skill / 文档流程可直接复用。
推荐验证:
printf 'blog-assets-feishu-check\n' | mc pipe local/blog-assets/feishu/acceptance-check/readme.txt
mc stat local/blog-assets/feishu/acceptance-check/readme.txt
curl -s http://tx2.898311.xyz:9010/blog-assets/feishu/acceptance-check/readme.txt
mc rm local/blog-assets/feishu/acceptance-check/readme.txt预期:
mc pipe成功mc stat成功curl返回blog-assets-feishu-check- 清理成功
步骤 8:更新自动化与使用约束
完成桶和策略配置后,还要补齐使用约束,避免后续重新回到“随意上传、覆盖、不可追溯”的状态。
至少确认以下事项:
- 文档和 skill 统一使用
http://tx2.898311.xyz:9010作为公网访问前缀 - 自动化脚本或 skill 可先复用现有环境变量:
GENERALANDROID_MC_ALIASGENERALANDROID_MC_ENDPOINTGENERALANDROID_MC_ACCESS_KEYGENERALANDROID_MC_SECRET_KEY这些变量只表示 MinIO 连接配置,不表示对象应上传到generalandroid桶;后续如需彻底消歧义,可再改为更中性的命名。
- 上传完成后必须执行
mc stat再输出 URL - 临时验收对象统一放
test/或acceptance-check/,完成后立即删除 - 不要把
minio.898311.xyz写成mc/ SDK endpoint
4. 最终验证清单
全部执行完成后,逐条检查:
| # | 检查项 | 命令 | 预期结果 |
|---|---|---|---|
| 1 | 桶已创建 | mc ls local | grep blog-assets | 列出 blog-assets/ |
| 2 | 匿名可下载 | curl -s http://127.0.0.1:9000/blog-assets/<test-file> | 返回文件内容 |
| 3 | 公网可访问 | curl -s http://tx2.898311.xyz:9010/blog-assets/<test-file> | 返回文件内容 |
| 4 | uploadrw 可写 | mc pipe uploadrw/blog-assets/test.txt | 成功 |
| 5 | uploadrw 可删 | mc rm uploadrw/blog-assets/test.txt | 成功 |
| 6 | 权限边界未扩大 | mc ls uploadrw/blogimg | Access Denied |
5. 使用方式
5.1 对象 key 规划
blog-assets 采用“单桶 + 分类前缀”模式,不再按多桶拆分。
推荐固定为三类主前缀:
blog-assets/
├── feishu/
│ └── <document_id>/
├── blog/
│ └── <yyyy>/<mm>/
└── shared/
└── <topic>/对应约束:
feishu/:飞书文档抓取产生的非图片附件,按document_id隔离blog/:普通博客文章附件,按年月归档shared/:长期复用的公共资源,按主题归档test/:仅用于验收或临时调试,不属于正式内容前缀
5.2 对象命名规则
对象命名优先保证:
- 来源可追溯
- URL 可读
- 不依赖覆盖同名对象更新内容
- 能避免缓存脏读和同名冲突
建议规则:
- 保留原始文件名的主体语义
- 文件名中的空格统一替换为
_ - 避免目录名和文件名中出现 shell 特殊字符
- 如果存在重名风险,在文件名末尾追加短 hash 或时间戳
示例:
blog-assets/feishu/UziqdXdeEo/perfetto_trace_reproduce-7f3a2c1d.pftrace
blog-assets/blog/2026/05/renderdoc-analysis-v2.pdf
blog-assets/shared/perfetto/analysis_skill-a13d90be.zip禁止把“覆盖上传同名文件”作为缓存失效手段。需要更新内容时,应直接生成新对象 key。
5.3 上传文件
飞书附件推荐:
mc cp /path/to/file.pftrace local/blog-assets/feishu/<document_id>/file.pftrace
mc stat local/blog-assets/feishu/<document_id>/file.pftrace普通博客附件推荐:
mc cp /path/to/attachment.pdf local/blog-assets/blog/2026/05/attachment.pdf
mc stat local/blog-assets/blog/2026/05/attachment.pdf公共共享资源推荐:
mc cp /path/to/archive.zip local/blog-assets/shared/perfetto/archive.zip
mc stat local/blog-assets/shared/perfetto/archive.zip上传动作完成后,必须再执行一次 mc stat 确认对象真实存在、size 正常,再把 URL 写入文档或脚本输出。
5.4 在文档中引用
Markdown 中直接使用公网 URL:
[分析报告.pdf](http://tx2.898311.xyz:9010/blog-assets/blog/2026/05/analysis-report.pdf)对于 trace / zip / 大附件,建议写明文件大小或用途:
[perfetto_trace_reproduce.pftrace (427MB)](http://tx2.898311.xyz:9010/blog-assets/feishu/abc123/perfetto_trace_reproduce.pftrace)5.5 缓存策略
当前阶段先稳定对象路径和命名规则,再考虑 CDN。
建议约束:
- 稳定附件默认按“可长缓存”设计
- 内容变更通过生成新对象 key实现,而不是覆盖原对象
- 文档中一旦引用外链,尽量不要复用同名对象承载不同内容
- 如果后续接 CDN,也继续复用“对象 key 版本化”的方式做缓存失效
5.6 生命周期与清理策略
当前建议按前缀区分保留策略:
| 前缀 | 默认策略 | 说明 |
|---|---|---|
feishu/ | 长期保留,后续按引用关系治理 | 先保证链接稳定,再考虑去重或清理 |
blog/ | 长期保留 | 已发布文档的附件不应随意删除 |
shared/ | 长期保留 | 公共复用资源,优先稳定 |
test/ | 短期保留 | 验收完成后立即清理 |
在自动生命周期规则真正落地前,至少保证:
- 所有
test/对象在验收后立即删除 - 临时实验对象不要混入
blog//shared/正式前缀 - 对正式资源的清理必须以“是否仍被引用”为前提,而不是只看上传时间
- 超大附件如果属于博客 / 知识库附件,也继续留在
blog-assets对应前缀,不迁回generalandroid
6. 回滚方案
如果需要撤销本次操作:
# 删除桶(桶必须为空才能删除)
mc rb local/blog-assets --force
# 恢复旧策略(去掉 blog-assets 相关行后重新应用)
mc admin policy create local upload-rw-policy <旧策略文件路径>7. 与现有架构的关系
| 桶 | 用途 | 访问方式 |
|---|---|---|
generalandroid | 仓库大文件迁移托管 / 研发资产恢复;不承接博客 / 知识库发布附件 | 需认证(mc alias) |
blogfile | 已有博客文件 | 需认证 |
upload | 通用上传 | 需认证 |
tmp-image | 临时图片 | 需认证 |
blog-assets(新) | 博客 / 知识库附件分发(含超大附件) | 匿名可下载,上传需认证 |
上传目标判定规则:只看内容归属,不看文件大小。凡属于博客 / 知识库对外引用附件,即使超大,也进入 blog-assets;generalandroid 不作为此类附件的兜底桶。
8. fetch-feishu skill 改造方案
8.1 当前行为(改造前)
飞书文档附件(非图片)
→ media_download 下载到本地 /tmp/feishu_images/
→ 无法上传图床(格式限制)
→ 保存到 attachments/ 子目录
→ 文档中用相对路径引用
8.2 改造后行为
飞书文档附件(非图片)
→ media_download 下载到本地 /tmp/feishu_attachments/
→ mc cp 上传到 blog-assets 桶
→ 获得公网 URL
→ 文档中用公网 URL 引用
8.3 skill 中需要修改的部分
在 fetch-feishu.md 中新增附件处理步骤,位于图片处理之后。
新增:附件上传命令
# 上传附件到 blog-assets(需要 mc 已配置 alias)
mc cp /tmp/feishu_attachments/<文件名> local/blog-assets/feishu/<document_id>/<文件名>
# 返回的公网 URL 格式:
# http://tx2.898311.xyz:9010/blog-assets/feishu/<document_id>/<文件名>新增:skill 输出中的附件引用格式
对于不同类型的附件,在 Markdown 中用不同方式引用:
# PDF / 文档类 → 链接
[分析报告.pdf](http://tx2.898311.xyz:9010/blog-assets/feishu/abc123/分析报告.pdf)
# Trace / 数据文件 → 链接 + 大小标注
[perfetto_trace.pftrace (427MB)](http://tx2.898311.xyz:9010/blog-assets/feishu/abc123/perfetto_trace.pftrace)
# Zip / 压缩包 → 链接 + 说明
[analysis_skill.zip (20KB)](http://tx2.898311.xyz:9010/blog-assets/feishu/abc123/analysis_skill.zip)修改:skill 的”供其他 skill 调用”部分
新增附件上传命令说明:
- 附件上传命令(非图片文件:trace、zip、pdf 等):
```bash
mc cp <附件路径> local/blog-assets/feishu/<document_id>/<文件名>公网访问 URL:http://tx2.898311.xyz:9010/blog-assets/feishu/<document_id>/<文件名>
### 8.4 目录规范
blog-assets/ └── feishu/ └── <document_id>/ ← 以飞书文档 ID 为目录名 ├── perfetto_xxx.pftrace ├── analysis_skill.zip └── report.pdf
用 document_id 作为子目录好处:
- 天然去重(同一文档多次爬取不会冲突)
- 方便追溯来源
- 清理时按文档维度操作
### 8.5 skill 可用的上传接口
桶创建完成后,skill 有以下方式完成附件上传:
#### 方式 A:mc cp(推荐,最简单)
```bash
mc cp <本地文件路径> local/blog-assets/feishu/<document_id>/<文件名>
返回公网 URL:
http://tx2.898311.xyz:9010/blog-assets/feishu/<document_id>/<文件名>
完整示例:
# 上传
mc cp /tmp/feishu_attachments/perfetto_trace.pftrace local/blog-assets/feishu/UziqdXdeEo/perfetto_trace.pftrace
# 验证上传成功
mc stat local/blog-assets/feishu/UziqdXdeEo/perfetto_trace.pftrace适用场景:执行 skill 的机器已安装 mc 且 alias 已配置。
方式 B:curl + S3 PUT(无需 mc,适合轻量环境)
因为桶设置了 public download,但上传仍需认证。可用 S3 签名方式 PUT:
# 需要 ACCESS_KEY 和 SECRET_KEY
ENDPOINT="http://tx2.898311.xyz:9010"
BUCKET="blog-assets"
OBJECT_KEY="feishu/<document_id>/<文件名>"
FILE="/tmp/feishu_attachments/<文件名>"
# 使用 mc 内置的 pipe 能力(比手写签名简单)
mc pipe local/${BUCKET}/${OBJECT_KEY} < "${FILE}"或者直接用 Python(如果 skill 在 Python 环境中执行):
import subprocess, os
def upload_to_blog_assets(local_path, document_id, filename, alias="local"):
"""上传文件到 blog-assets 桶,返回公网 URL"""
object_key = f"blog-assets/feishu/{document_id}/{filename}"
cmd = ["mc", "cp", local_path, f"{alias}/{object_key}"]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
return f"http://tx2.898311.xyz:9010/{object_key}"
return None方式 C:扩展现有上传页 API(可选,需改服务端)
当前图床 API 接口格式:
curl -sS -X POST https://minio.898311.xyz/upload/api/upload \
-F "file=@<文件路径>" -F "bucket=blog-assets" | jq -r '.url'但当前服务端 只允许图片格式。如果后续扩展上传页支持任意文件类型 + blog-assets 桶,则 skill 可以统一用这个 HTTP API,无需 mc。
当前推荐方式 A:mc cp 最简单可靠,且执行 skill 的机器(本机)已有 mc。
接口对比表
| 方式 | 命令 | 依赖 | 适用场景 |
|---|---|---|---|
A: mc cp | mc cp <file> local/blog-assets/... | mc + alias | 本机 skill 执行(推荐) |
B: mc pipe | mc pipe local/blog-assets/... < file | mc + alias | stdin 管道场景 |
| C: curl PUT | 需手写 AWS4 签名 | curl | 无 mc 环境(复杂,不推荐) |
| D: 上传页 API | curl -F file=@... -F bucket=blog-assets | curl | 需服务端改造后可用 |
skill 改造后的附件上传伪代码
# 在 fetch-feishu skill 的附件处理步骤中:
DOCUMENT_ID="<从步骤1解析出的document_id>"
ATTACH_DIR="/tmp/feishu_attachments"
for file in ${ATTACH_DIR}/*; do
filename=$(basename "$file")
# 上传
mc cp "$file" "local/blog-assets/feishu/${DOCUMENT_ID}/${filename}"
# 生成 URL
URL="http://tx2.898311.xyz:9010/blog-assets/feishu/${DOCUMENT_ID}/${filename}"
# 在 Markdown 中替换为 URL 引用
echo "[${filename}](${URL})"
done8.6 skill 改造的前置条件
| 条件 | 说明 |
|---|---|
blog-assets 桶已创建 | 本文档步骤 1-6 全部完成 |
执行 skill 的机器有 mc | 且 local alias 或环境变量已配置 |
mc cp 可成功写入 blog-assets | 步骤 5 验证通过 |
8.7 兜底逻辑
如果 mc 不可用或上传失败:
- 回退到当前行为(本地
attachments/+ 相对路径引用) - 在输出中标注哪些附件未能上传
8.8 改造后的 skill 输出示例
文档已保存: /home/zbc/pangu/GeneralAndroid/main/爬取的文章/<标题>.md
- 标题: xxx
- 字数: xxx
- 图片: 3/3 张已上传图床
- 附件: 3/3 个已上传 blog-assets
├─ perfetto_1779783908.pftrace → http://tx2.898311.xyz:9010/blog-assets/feishu/abc123/perfetto_1779783908.pftrace
├─ perfetto_trace_reproduce.pftrace → http://tx2.898311.xyz:9010/blog-assets/feishu/abc123/perfetto_trace_reproduce.pftrace
└─ analysis_skill.zip → http://tx2.898311.xyz:9010/blog-assets/feishu/abc123/analysis_skill.zip
9. 完整实施顺序
- 服务器操作(本文档步骤 1-8):建桶 → 设匿名 → 更新策略 → 验证 → 补齐使用约束
- skill 改造(本文档 §8):修改
fetch-feishu.md,新增附件上传逻辑 - 验证闭环:爬一篇带附件的飞书文档,确认附件公网 URL 可访问且上传后有
mc stat校验
10. 验收标准
本方案完成后,至少要满足以下验收项:
| # | 验收项 | 通过标准 |
|---|---|---|
| 1 | blog-assets 桶已创建 | mc ls local 可见 blog-assets/ |
| 2 | 匿名下载生效 | 本地和公网 curl 都能读取测试对象 |
| 3 | uploadrw0511a 可读写删 | mc ls / mc pipe / mc cat / mc rm 均成功 |
| 4 | 权限边界未扩大 | 对未授权桶仍返回 Access Denied |
| 5 | 正式前缀可用 | feishu/ 或 blog/ 前缀上传、读取、清理闭环通过 |
| 6 | URL 规范统一 | 文档与脚本都使用 http://tx2.898311.xyz:9010 |
| 7 | 上传校验规则落实 | 所有示例都体现“上传后必须 mc stat” |
| 8 | 清理规则明确 | 验收测试对象会被删除,不混入正式前缀 |
11. 已完成事项 / 待完成事项
11.1 本次文档已完成
- 明确
blog-assets的定位为公开附件分发桶 - 补充公网 endpoint 统一约束
- 补充单桶 + 分类前缀目录规范
- 补充对象命名规则、缓存规则、生命周期规则
- 补充正式前缀验收与自动化约束
- 明确上传后必须
mc stat校验
11.2 仍需实际执行的事项
以下事项当前只是写入方案,还没有被实际执行:
- 在 MinIO 上真实创建
blog-assets桶 - 对
blog-assets应用匿名下载权限 - 修改并重新应用
main/blog/devops/misc/minio-upload-rw-policy.json - 验证
uploadrw0511a对blog-assets的真实读写删能力 - 验证正式前缀和公网 URL 的真实可用性
- 修改
fetch-feishuskill,把非图片附件切换到blog-assets
如果要继续推进到“方案落地完成”,下一步应直接执行这些命令并逐项验收,而不是只停留在文档层。
12. 后续可选增强
当前方案已满足基本需求。后续如有需要可继续扩展:
- 大文件上传治理:为
blog-assets单独定义超大附件命名规范、上传校验和分目录策略,但仍然留在blog-assets - CDN 缓存:在 frpc 前加一层 Cloudflare 或 Nginx 缓存
- HTTPS:如果需要 HTTPS URL,在反代层加证书(目前
tx2.898311.xyz:9010是 HTTP) - 生命周期规则:对临时测试文件设置自动过期清理
- 上传页扩展:如果图床
minio.898311.xyz的上传 API 也支持非图片格式,则可以不走mc而直接走 HTTP API