☰
Codex 实战 Skills:用 TaoToken 统一 Key 编写批量防伪加密水印 Skill
2026/9/30 23:03:56 网站建设 项目流程

1. 保密文档批处理的真实痛点与 Codex Skills 的切入点

如果你在企业里负责过文档分发,大概率遇到过这种场景:法务部要你把一份 100 页的技术白皮书发给 30 个供应商,每份都要带上对方公司名的防伪水印,还要单独设一个打开密码。手动用 Acrobat 一份份加?一天就没了,而且中途漏掉一份没加密,后果可能比加班更严重。

我试过用纯 Python 脚本硬扛,结果卡在两个地方:一是水印字体和透明度在不同 PDF 上表现不一致,二是加密权限的位掩码写错一位,打印权限就全开了。后来把整套流程封装成 Codex Skill,才算把「输入约定 → 水印生成 → 加密输出 → 校验解密」这条链路固定下来。

Codex Skills 在这里的价值,不是帮你写代码,而是把「批量防伪加密水印」这件事变成一个可复用、可版本管理的原子能力。你只需要约定好输入目录、水印模板、密码策略,剩下的扫描、渲染、加密、日志全部自动跑完。适合谁?适合需要定期向外部合作方分发保密 PDF 的运维、安全工程师,以及想把文档 DLP 流程自动化的后端开发。

这一篇我会以 100 份文档为样本,拆解 Skill 的输入约定、水印生成与加密输出流程,给出可复制的 Skill 配置片段和 TaoToken 统一 Key 接入示例,最后附上批量运行后的水印校验与解密验证动作。你跟着做,基本能一次跑通。

2. TaoToken 统一 Key 前置:让 Skill 调用模型时不再散落密钥

Codex Skills 在运行过程中,如果需要调用大模型来做水印文案生成、文档摘要或异常判断,就会涉及 API Key 的管理。传统做法是把 Key 硬编码在脚本里,或者每个 Skill 单独配一份环境变量,结果就是密钥散落、轮换困难、审计无门。TaoToken 在这里的角色,是提供一个统一的 API 入口,让你用同一个 Key 驱动多个 Skill 的模型调用。

先明确几个地址,后面配置会反复用到:

  • 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
  • API 基地址:https://taotoken.net/api
  • 模型对话页:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
  • Coding Plan 页:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
  • 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
  • API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
  • Claude Code 接入:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite

拿到 Key 之后,不要直接写进 Skill 的源码。推荐的做法是在项目根目录建一个.env文件,把 Key 和 Base URL 放进去,Skill 运行时通过环境变量读取。这样你在 Codex 里切换不同 Skill 时,只需要维护一份密钥配置。

具体操作:登录控制台后进入 API Keys 页面,创建一个新 Key,复制出来。然后在你的 Skill 项目目录下创建.env:

# .env TAOTOKEN_API_KEY=sk-你的实际Key TAOTOKEN_BASE_URL=https://taotoken.net/api

接着在 Skill 的入口脚本里加载:

import os from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("TAOTOKEN_API_KEY") BASE_URL = os.getenv("TAOTOKEN_BASE_URL") if not API_KEY: raise RuntimeError("TAOTOKEN_API_KEY 未设置,请检查 .env 文件")

这里有个容易踩的坑:.env一定要加进.gitignore,否则 Key 会跟着代码进仓库。另外,如果你在 Codex 的 Skill 配置里直接写env字段,注意不要和系统环境变量冲突,优先级是 Skill 配置 > 系统环境变量。

对于需要长期跑批量任务的场景,比如每天定时处理 100 份文档,建议用 Coding Plan 的额度,比按次调用更划算,而且 Key 的权限可以单独限制,避免一个 Key 被所有 Skill 共用导致审计混乱。配置完成后,你可以先用模型对话页发一条测试请求,确认 Key 和 Base URL 都能通,再进入下一步的 Skill 编写。

3. 可复制的 Skill 配置:输入约定、水印生成与加密输出

这一节是核心,我会给出完整的 Skill 配置片段和代码结构。先约定输入输出,再拆水印生成,最后做加密输出。

3.1 输入约定与目录结构

Skill 的输入约定必须固定,否则批量跑的时候文件名一乱就找不到对应关系。我采用的约定是:

skill_workspace/ ├── input_pdfs/ # 原始 PDF,命名规则:{接收方}_{文档名}.pdf │ ├── ClientA_Proposal.pdf │ ├── ClientB_Proposal.pdf │ └── ... ├── output_pdfs/ # 加密后的 PDF,命名规则:secure_{原文件名} ├── config/ │ └── skill_config.json ├── logs/ │ └── watermark_process.log └── skill.py

skill_config.json是 Skill 的配置中心,所有可变参数都放这里,避免改代码:

{ "input_folder": "./input_pdfs", "output_folder": "./output_pdfs", "watermark": { "text_template": "CONFIDENTIAL - {recipient} ONLY", "font_path": "/System/Library/Fonts/PingFang.ttc", "font_size": 42, "transparency": 0.15, "rotation": 45, "color_rgb": [0.5, 0.5, 0.5] }, "encryption": { "user_password_template": "Open_{recipient}_2024", "owner_password": "OwnerSecure_2024", "allow_printing": false, "allow_copying": false, "allow_modifying": false, "algorithm": "AES-256" }, "model": { "base_url": "https://taotoken.net/api", "model_id": "gpt-4o-mini", "api_key_env": "TAOTOKEN_API_KEY" } }

注意text_template和user_password_template里的{recipient}占位符,Skill 会从文件名里解析出接收方名称,动态替换。这样 100 份文档就能自动生成 100 个不同的水印和密码,不需要手动改。

3.2 水印生成:reportlab Canvas 的关键参数

水印生成用 reportlab 的 Canvas,核心是透明度、旋转和字体注册。下面这段代码可以直接复制:

from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont def register_font(font_path: str) -> str: if font_path and os.path.exists(font_path): pdfmetrics.registerFont(TTFont("CustomFont", font_path)) return "CustomFont" return "Helvetica" def create_watermark_pdf(text: str, font_name: str, cfg: dict, out_path: str): c = canvas.Canvas(out_path, pagesize=A4) width, height = A4 c.setFillAlpha(cfg["transparency"]) c.setFont(font_name, cfg["font_size"]) c.setFillColorRGB(*cfg["color_rgb"]) c.saveState() c.translate(width / 2, height / 2) c.rotate(cfg["rotation"]) text_width = c.stringWidth(text, font_name, cfg["font_size"]) c.drawString(-text_width / 2, -cfg["font_size"] / 2, text) c.restoreState() c.save() return out_path

这里有几个参数需要你根据实际文档调整。transparency设成 0.15 是比较安全的区间,既能看清又不遮挡正文;rotation用 45 度是行业惯例,裁剪难度高;font_size42 在 A4 上大约占页面宽度的三分之一,视觉上够醒目。如果你用的是中文字体,font_path在 macOS 上可以指向 PingFang.ttc,Windows 上指向 simhei.ttf,Linux 上如果没有中文字体,建议水印文案先用英文,避免出现方框乱码。

3.3 加密输出:权限位掩码与 AES-256

加密部分用 pypdf 的encrypt方法,关键是权限位掩码别写错。下面是对照表:

权限位掩码说明
打印0x04允许打印文档
复制0x08允许复制文本和图形
修改0x10允许修改文档内容
注释0x20允许添加注释

如果你要禁止打印、复制、修改,权限码就是 0。代码里这样构建:

from pypdf import PdfReader, PdfWriter def encrypt_pdf(input_path, output_path, watermark_path, user_pw, owner_pw, cfg): reader = PdfReader(input_path) watermark_page = PdfReader(watermark_path).pages[0] writer = PdfWriter() for page in reader.pages: page.merge_page(watermark_page, over=True) writer.add_page(page) perm_code = 0 if cfg["allow_printing"]: perm_code |= 0x04 if cfg["allow_copying"]: perm_code |= 0x08 if cfg["allow_modifying"]: perm_code |= 0x10 writer.encrypt( user_password=user_pw, owner_password=owner_pw, permissions_flag=perm_code, algorithm="AES-256" ) with open(output_path, "wb") as f: writer.write(f)

merge_page的over=True表示水印覆盖在正文之上,配合 0.15 的透明度,效果就是水印浮在文字上方但不影响阅读。如果你希望水印在文字下方,改成over=False,但要注意有些 PDF 的图层顺序会导致水印被完全遮住,实测下来over=True更稳定。

3.4 批量调度与日志

批量处理用pathlib扫描目录,每个文件独立 try-except,单个失败不中断整体:

from pathlib import Path import logging def process_batch(config: dict): input_dir = Path(config["input_folder"]) output_dir = Path(config["output_folder"]) output_dir.mkdir(parents=True, exist_ok=True) pdf_files = list(input_dir.glob("*.pdf")) success, fail = 0, 0 for pdf_file in pdf_files: recipient = pdf_file.stem.split("_")[0] watermark_text = config["watermark"]["text_template"].format(recipient=recipient) user_pw = config["encryption"]["user_password_template"].format(recipient=recipient) try: wm_path = create_watermark_pdf(watermark_text, font_name, config["watermark"], "temp_wm.pdf") encrypt_pdf( str(pdf_file), str(output_dir / f"secure_{pdf_file.name}"), wm_path, user_pw, config["encryption"]["owner_password"], config["encryption"] ) success += 1 logging.info(f"成功: {pdf_file.name}") except Exception as e: fail += 1 logging.error(f"失败: {pdf_file.name}, 原因: {e}") logging.info(f"批量完成,成功 {success},失败 {fail}")

跑完 100 份文档,日志里会清楚记录每一份的状态。如果某一份因为字体缺失或 PDF 损坏失败,其他 99 份不受影响。

4. 验证请求与成功结果:水印校验与解密验证

批量跑完之后,不能只看日志说成功就完事,必须做两步验证:水印是否真的盖上去了,加密是否真的生效了。

4.1 水印校验

最直接的方法是用 pypdf 读取输出文件,检查页面内容流里是否包含水印文本。但更实用的是用命令行工具快速抽检:

# 用 pdftotext 提取文本,看水印文字是否出现 pdftotext output_pdfs/secure_ClientA_Proposal.pdf - | grep "CONFIDENTIAL"

如果输出里有CONFIDENTIAL - ClientA ONLY,说明水印文本已经写入。但文本提取只能验证文字存在,验证不了透明度和旋转。要验证视觉效果,建议用 Python 渲染第一页为图片:

from pdf2image import convert_from_path images = convert_from_path("output_pdfs/secure_ClientA_Proposal.pdf", first_page=1, last_page=1) images[0].save("check_watermark.png")

打开图片,你应该能看到 45 度倾斜的灰色半透明水印,覆盖在正文上方。如果水印太淡或太浓,回去调transparency参数。

4.2 解密验证

加密验证要确认两件事:用正确密码能打开,用错误密码打不开,且权限限制生效。

from pypdf import PdfReader # 正确密码 reader = PdfReader("output_pdfs/secure_ClientA_Proposal.pdf") if reader.is_encrypted: result = reader.decrypt("Open_ClientA_2024") print(f"解密结果: {result}") # 应该输出 1 或 2,表示成功 print(f"页数: {len(reader.pages)}") # 错误密码 reader2 = PdfReader("output_pdfs/secure_ClientA_Proposal.pdf") if reader2.is_encrypted: result2 = reader2.decrypt("WrongPassword") print(f"错误密码解密结果: {result2}") # 应该输出 0,表示失败

如果正确密码返回 1 或 2,错误密码返回 0,说明加密生效。权限验证可以用reader.user_access_permissions查看,确认打印和复制都是 False。

4.3 用 TaoToken 模型做异常摘要

100 份文档跑完,日志可能有几百行。你可以用 TaoToken 的模型对话能力,把日志丢给模型做异常摘要。配置如下:

import requests def summarize_log(log_text: str): resp = requests.post( f"{BASE_URL}/v1/chat/completions", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "model": "gpt-4o-mini", "messages": [ {"role": "system", "content": "你是一个日志分析助手,请提取失败项和原因。"}, {"role": "user", "content": log_text} ] } ) return resp.json()["choices"][0]["message"]["content"]

这样你不需要逐行翻日志,模型会直接告诉你哪几份失败了、可能的原因是什么。注意 Base URL 用https://taotoken.net/api,不要加 UTM 参数,那是给网页用的。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

批量跑 Skill 的时候,报错集中在几个地方。我按真实遇到的顺序列出来,你对照排查。

5.1 401 Unauthorized

这是最常见的,通常是 Key 没读到或 Key 无效。检查顺序:

  1. .env文件是否在 Skill 运行目录下,load_dotenv()是否在读取 Key 之前调用。
  2. 环境变量名是否和代码里一致,比如代码读TAOTOKEN_API_KEY,.env里写的是TAOTOKEN_KEY,那就读不到。
  3. Key 是否被复制时带了空格或换行,建议用strip()处理。
  4. 如果用的是 Coding Plan 的 Key,确认该 Key 是否有权限调用你指定的模型。

修复后重新跑一份文档测试,不要直接跑 100 份。

5.2 local proxy failed

这个报错通常出现在你本地网络环境有代理设置,但 Skill 请求 TaoToken API 时走了代理导致连接失败。检查:

echo $HTTP_PROXY echo $HTTPS_PROXY

如果有值,在 Skill 里显式禁用代理:

import os os.environ["HTTP_PROXY"] = "" os.environ["HTTPS_PROXY"] = ""

或者在 requests 调用时加proxies={"http": None, "https": None}。注意不要用任何非正规的网络工具,直接连 TaoToken 的 API 地址即可。

5.3 reading 'choices' 报错

这个报错说明模型返回的 JSON 结构里没有choices字段,通常是请求体格式不对或模型 ID 写错。检查:

  1. model字段是否拼写正确,比如gpt-4o-mini不要写成gpt4o-mini。
  2. 请求头Content-Type是否为application/json。
  3. 如果返回的是错误信息,先打印resp.text看完整内容,再定位。
resp = requests.post(...) print(resp.status_code) print(resp.text) # 先看原始返回 data = resp.json() if "choices" in data: content = data["choices"][0]["message"]["content"] else: raise RuntimeError(f"模型返回异常: {data}")

5.4 OAuth 相关报错

如果你在 Codex 里配置了 OAuth 方式的接入,报错通常是 token 过期或回调地址不匹配。检查:

  1. 回调地址是否和控制台里配置的一致,包括端口号。
  2. token 是否过期,重新走一次授权流程。
  3. 如果同时配了 API Key 和 OAuth,确认 Skill 实际用的是哪一种,不要混用。

对于批量文档处理这种场景,我建议直接用 API Key,比 OAuth 简单,而且 Key 可以单独限制权限,审计更方便。

5.5 三件套检查清单

如果你用的是 CC Switch、Cline MCP 或 Codex 的auth.json,出现连接问题时,按这三件套逐项核对:

配置项正确值常见错误
Base URLhttps://taotoken.net/api写成带 UTM 的网页地址
API Keysk-开头复制时漏字符或带空格
Model IDgpt-4o-mini等拼写错误或用了不存在的模型

auth.json的配置示例:

{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "gpt-4o-mini" }

三项都对上,基本不会出现连接问题。如果还报错,先去模型对话页发一条测试消息,确认 Key 本身可用,再排查 Skill 侧。

6. 语义一致 CTA:把 Skill 接入你的文档安全流程

到这里,你已经有了一个能跑通 100 份文档的防伪加密水印 Skill。接下来要做的,是把它接入你日常的文档分发流程。

如果你在排障或接入阶段遇到问题,先去 API Keys 页面确认 Key 状态,再对照接入文档检查 Base URL 和 Model ID。文档里有完整的请求示例和错误码说明,比在代码里猜要快。

如果你想先验证模型调用是否正常,用模型对话页发一条测试消息,确认返回结构里有choices字段,再回到 Skill 里跑批量任务。

如果你打算把这个 Skill 做成每天定时跑的长期任务,比如每天早上 8 点自动处理前一天的待分发文档,建议用 Coding Plan 的额度,Key 的权限可以单独限制在文档处理相关的模型上,避免和其他业务混用。

最后提醒一个实操细节:批量跑之前,先用 3 份文档做小样本测试,确认水印位置、透明度、密码规则都符合预期,再放开到 100 份。我踩过的坑是第一次直接跑全量,结果水印字体没注册成功,100 份全变成方框,只能删掉重来。小样本测试花 5 分钟,能省你半小时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询