pan-skill:博客网盘Skill 重构记录:从单功能上传到全能文件网盘管理器

2026年7月27日 兴趣使然 10 分钟阅读 2 次阅读
📖 文章摘要

从最初只会上传文件到后来能查列表、给分享链接、生成直链、删改文件,再到结合实际对话反馈优化工作流——记录一个 Hermes Agent Skill 的完整迭代过程。

博客网盘 Skill 重构记录:从单功能上传到全能文件管理器

背景

我的博客是 Nuxt 3 + FastAPI 的全栈项目,带了一个完整的网盘功能(Pan),支持密码保护、下载限制、过期时间、文件夹分类。之前为了给 QQ 机器人提供文件发送能力,做了一个基础的 upload 脚本,让机器人能上传文件到网盘然后返回下载链接。

最初的版本很简单:一个 Python 脚本,硬编码传 upload,固定 10 次下载、1 天过期、8 位密码。SKILL.md 也是随便写的,描述上传流程,给机器人照做。

用了一段时间后,问题开始暴露。

需求升级

QQ 机器人上线后,用户(其实就是我)的请求越来越多样:

  • "帮我上传这个文件" — upload,没问题
  • "看看网盘里有什么" — 没命令,只能让机器人翻文件
  • "123.txt 的链接给我" — 没命令,机器人答不上来,不知道是分享链接还是直链
  • "之前那个文件的直链" — 没命令,机器人只能绕路
  • "帮我改了那个文件的密码和下载次数" — 没命令
  • "把这个文件删了" — 没命令

痛点很清晰:脚本只有 upload,但实际场景需要上传、查看、给链接、生成直链、修改、删除六种操作。每次要让机器人做 upload 之外的事,都需要手动修脚本或者人工处理。

设计思路:多模式 CLI

决定大改。参考已有的 article Skill(博客文章发布)的设计思路,把脚本改成多模式 CLI,用 argparse 子命令区分操作:

upload      — 上传文件
list         — 查看文件列表
share-link   — 获取分享链接+密码
direct-link  — 生成直链
delete       — 删除文件
update       — 修改属性

每个模式对应一个后端 API 端点,用 _request 统一封装 HTTP 请求。

默认值统一

之前的版本三方说法各不同:

  • SKILL.md 写「下载次数 30 次,过期 30 天,密码 4 位」
  • 脚本硬编码 download_limit=10, expire_days=1
  • 后端 API 默认 0(不限制)

重构后统一为:30 次下载、30 天过期、4 位密码,写入脚本和 SKILL.md。

核心实现

认证复用

认证逻辑没变:从 env 文件读博客地址和账号密码,自动 /api/auth/login 获取 JWT token,内存缓存 7 天。所有请求共享同一个登录态。

关键改进是用 urllib.parse.urlencode 替换了原来的字符串拼接 query params,避免密码含 &# 等特殊字符时 URL 截断。

文件查找:_resolve_file

六个命令里有四个(share-link、direct-link、delete、update)都需要先找到文件。提炼了 _resolve_file 函数:

def _resolve_file(query, folder=None) -> dict:
    # 1. 纯数字 → 按 ID 精确匹配
    # 2. 字符串 → 按文件名模糊搜索(跨所有文件夹)
    # 3. 无匹配 → exit 并提示
    # 4. 多匹配 → 列出所有让调用方选择
    # 5. 唯一匹配 → 返回文件 dict

这个函数被四个命令共用,避免了重复代码。匹配逻辑先按 ID(数字)精确查,不行再按文件名关键字搜索,不区分大小写。如果搜到多个同名文件,列出 ID 和大小让用户选。

直链生成

后端直链接口 POST /api/pan/{id}/direct 每次调用都会生成一个新的 token(secrets.token_hex(32)),之前的不失效。所以每次用户要直链就调一次,后端生成新的,返回完整 URL。

对这个行为,SKILL.md 里注明:直链无密码保护,任何人可下载,仅在你要求时生成

文件流式上传

原来的 multipart 构造是字符串+字节混拼:

body = ""
body += f"--{boundary}\r\n"
body_bytes = body.encode() + file_data + ...

重构后用纯 bytes 分段构建,逻辑更清晰:

parts = [
    f"--{boundary}\r\n".encode(),
    b"Content-Type: application/octet-stream\r\n\r\n",
]
with open(file_path, "rb") as f:
    parts.append(f.read())
return b"".join(parts)

SKILL.md 迭代:从参数表格到决策树

一开始的 SKILL.md 是按命令组织的,每个命令配参数表格、示例输出。结果实际测试时,机器人拿着文档走流程发现几个问题。

问题一:4 个问题串行问太啰嗦

原 workflow 要求机器人依次问四项:

① 下载次数多少? ② 过期几天? ③ 自己设密码还是自动生成? ④ 要不要直链?

实际场景中,用户大部分时候想用默认值(30次/30天/自动密码),被问四次很烦。机器人自己优化成了"默认还是自定义?"一次问完,我采纳了这个改进写进 SKILL.md。

问题二:上传+直链分不清

用户说"帮我生成一个直链"但发了文件,原 workflow 走到了"直链"分支,调 direct-link 去查已有文件,绕了一圈。实际上用户是想上传新文件并直接拿直链。

修复后的决策树:

├─ 明确说"直链" →
│   ├─ 附带文件 → upload --direct(上传+直链一步到位)
│   └─ 没文件,只提文件名 → direct-link <文件名或ID>

问题三:关键词死板

只写了"链"/"分享"/"下载链接",用户说"发我""给个地址""提取这个文件"就不认识了。扩展了同义表达范围("存一下""发我""提取""地址""续期"等)。

最终结构

重构后的 SKILL.md 从 6KB 精简到 3.4KB,去掉了参数表格、示例输出这些机器人不需要的细节,保留核心:

  • When to Use — 触发条件 + 同义表达示例
  • Workflow — 决策树,机器人照着走
  • Commands — 一行签名,带默认值
  • Notes — 安全、行为说明

踩坑记录

urlencode 而不是字符串拼接

初版脚本手动拼 query string:

params = f"password={password}&download_limit={limit}"

如果密码包含 &+# 等字符,URL 会被截断。换成 urllib.parse.urlencode 后自动处理转义。

_resolve_file 的死代码

_resolve_file 内部处理了未找到和多个匹配的情况——都调 sys.exit()。但是调用方还写了一堆防御性检查:

matches = _resolve_file(...)
if not matches:
    return
if isinstance(matches, list):
    return

这些永远不会走到,纯属误导。重构后统一删掉,调用方直接拿结果用。

multipart 字符串+字节混拼

原始脚本用 body.encode() + file_data + ...encode() 构造 multipart,字符串到字节的转换点容易出错。改用纯 bytes 分段拼接后逻辑更清晰。

update --expire-days 是重置不是续期

最初没意识到后端 PUT /api/pan/{id}expire_at 是直接重设过期时间,不是"在原来基础上延长"。如果用户说"再延 7 天",传 --expire-days 7 会从今天开始算 7 天,而不是从原过期日往后加。SKILL.md 里专门写了这点,机器人在用户说"续期"时需告知。

最终功能

图片
图片

upload <file>
  [--password] [--password-length] [--download-limit 30]
  [--expire-days 30] [--direct] [--folder robot_uploads]

list [--folder] [--search]
  默认 robot_uploads,--folder . 表示根目录

share-link <query> [--folder]
  返回分享链接+密码

direct-link <query> [--folder]
  自动生成直链

delete <query> [--folder]
  删除文件,操作不可逆

update <query>
  [--password] [--download-limit] [--expire-days]
  只改传了的字段

总结

这次重构的几个关键经验:

  1. 不要等到需求明确再设计:一开始只有上传,但多模式 CLI 的扩展成本很低——加一个子命令加一段 handler,不改现有逻辑。预留扩展点比之后大改省事得多。

  2. Workflow 比 API 文档更重要:对 Agent 来说,知道"什么时候调用什么命令"比知道"每个命令有哪些参数"关键得多。决策树式的工作流让机器人在复杂对话中不迷路。

  3. 让机器人的使用反馈驱动优化:重构后的第一个版本 workflow 写得"完美",结果机器人实际跑了几轮就发现串行问参数很蠢。让实际对话数据说话,别坐在那里猜什么是最好的交互方式。

  4. 标准化错误处理节省大量时间:统一 _request 封装 HTTP 请求和错误解析,统一 _resolve_file 处理文件查找的三种结果(找到/找不到/多匹配),四个命令白捡这些能力。

  5. Skil 文档的精简原则:每次加载 SKILL.md 都在消耗上下文的 Token。参数和示例输出是运行时可见的,不需要写在文档里;决策树和规则才是 AI Agent 真正需要的东西。

文章创建于:2026年7月27日CC BY-NC-SA 4.0
📡

评论

暂无评论,来写第一条吧

© 2026 My Blog. Built with Nuxt.js + FastAPI.