 Skills:用 TaoToken 統(tǒng)一 Key 編寫批量防偽加密水印 Skill)
1. 保密文檔批處理的真實痛點與 Codex Skills 的切入點如果你在企業(yè)里負責過文檔分發(fā)大概率遇到過這種場景法務部要你把一份 100 頁的技術(shù)白皮書發(fā)給 30 個供應商每份都要帶上對方公司名的防偽水印還要單獨設(shè)一個打開密碼。手動用 Acrobat 一份份加一天就沒了而且中途漏掉一份沒加密后果可能比加班更嚴重。我試過用純 Python 腳本硬扛結(jié)果卡在兩個地方一是水印字體和透明度在不同 PDF 上表現(xiàn)不一致二是加密權(quán)限的位掩碼寫錯一位打印權(quán)限就全開了。后來把整套流程封裝成 Codex Skill才算把「輸入約定 → 水印生成 → 加密輸出 → 校驗解密」這條鏈路固定下來。Codex Skills 在這里的價值不是幫你寫代碼而是把「批量防偽加密水印」這件事變成一個可復用、可版本管理的原子能力。你只需要約定好輸入目錄、水印模板、密碼策略剩下的掃描、渲染、加密、日志全部自動跑完。適合誰適合需要定期向外部合作方分發(fā)保密 PDF 的運維、安全工程師以及想把文檔 DLP 流程自動化的后端開發(fā)。這一篇我會以 100 份文檔為樣本拆解 Skill 的輸入約定、水印生成與加密輸出流程給出可復制的 Skill 配置片段和 TaoToken 統(tǒng)一 Key 接入示例最后附上批量運行后的水印校驗與解密驗證動作。你跟著做基本能一次跑通。2. TaoToken 統(tǒng)一 Key 前置讓 Skill 調(diào)用模型時不再散落密鑰Codex Skills 在運行過程中如果需要調(diào)用大模型來做水印文案生成、文檔摘要或異常判斷就會涉及 API Key 的管理。傳統(tǒng)做法是把 Key 硬編碼在腳本里或者每個 Skill 單獨配一份環(huán)境變量結(jié)果就是密鑰散落、輪換困難、審計無門。TaoToken 在這里的角色是提供一個統(tǒng)一的 API 入口讓你用同一個 Key 驅(qū)動多個 Skill 的模型調(diào)用。先明確幾個地址后面配置會反復用到官網(wǎng)入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基地址https://taotoken.net/api模型對話頁https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewriteCoding Plan 頁https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite控制臺https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文檔https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteClaude Code 接入https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite拿到 Key 之后不要直接寫進 Skill 的源碼。推薦的做法是在項目根目錄建一個.env文件把 Key 和 Base URL 放進去Skill 運行時通過環(huán)境變量讀取。這樣你在 Codex 里切換不同 Skill 時只需要維護一份密鑰配置。具體操作登錄控制臺后進入 API Keys 頁面創(chuàng)建一個新 Key復制出來。然后在你的 Skill 項目目錄下創(chuàng)建.env# .env TAOTOKEN_API_KEYsk-你的實際Key TAOTOKEN_BASE_URLhttps://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 未設(shè)置請檢查 .env 文件)這里有個容易踩的坑.env一定要加進.gitignore否則 Key 會跟著代碼進倉庫。另外如果你在 Codex 的 Skill 配置里直接寫env字段注意不要和系統(tǒng)環(huán)境變量沖突優(yōu)先級是 Skill 配置 系統(tǒng)環(huán)境變量。對于需要長期跑批量任務的場景比如每天定時處理 100 份文檔建議用 Coding Plan 的額度比按次調(diào)用更劃算而且 Key 的權(quán)限可以單獨限制避免一個 Key 被所有 Skill 共用導致審計混亂。配置完成后你可以先用模型對話頁發(fā)一條測試請求確認 Key 和 Base URL 都能通再進入下一步的 Skill 編寫。3. 可復制的 Skill 配置輸入約定、水印生成與加密輸出這一節(jié)是核心我會給出完整的 Skill 配置片段和代碼結(jié)構(gòu)。先約定輸入輸出再拆水印生成最后做加密輸出。3.1 輸入約定與目錄結(jié)構(gòu)Skill 的輸入約定必須固定否則批量跑的時候文件名一亂就找不到對應關(guān)系。我采用的約定是skill_workspace/ ├── input_pdfs/ # 原始 PDF命名規(guī)則{接收方}_{文檔名}.pdf │ ├── ClientA_Proposal.pdf │ ├── ClientB_Proposal.pdf │ └── ... ├── output_pdfs/ # 加密后的 PDF命名規(guī)則secure_{原文件名} ├── config/ │ └── skill_config.json ├── logs/ │ └── watermark_process.log └── skill.pyskill_config.json是 Skill 的配置中心所有可變參數(shù)都放這里避免改代碼{ 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 會從文件名里解析出接收方名稱動態(tài)替換。這樣 100 份文檔就能自動生成 100 個不同的水印和密碼不需要手動改。3.2 水印生成reportlab Canvas 的關(guān)鍵參數(shù)水印生成用 reportlab 的 Canvas核心是透明度、旋轉(zhuǎn)和字體注冊。下面這段代碼可以直接復制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, pagesizeA4) 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這里有幾個參數(shù)需要你根據(jù)實際文檔調(diào)整。transparency設(shè)成 0.15 是比較安全的區(qū)間既能看清又不遮擋正文rotation用 45 度是行業(yè)慣例裁剪難度高font_size42 在 A4 上大約占頁面寬度的三分之一視覺上夠醒目。如果你用的是中文字體font_path在 macOS 上可以指向 PingFang.ttcWindows 上指向 simhei.ttfLinux 上如果沒有中文字體建議水印文案先用英文避免出現(xiàn)方框亂碼。3.3 加密輸出權(quán)限位掩碼與 AES-256加密部分用 pypdf 的encrypt方法關(guān)鍵是權(quán)限位掩碼別寫錯。下面是對照表權(quán)限位掩碼說明打印0x04允許打印文檔復制0x08允許復制文本和圖形修改0x10允許修改文檔內(nèi)容注釋0x20允許添加注釋如果你要禁止打印、復制、修改權(quán)限碼就是 0。代碼里這樣構(gòu)建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, overTrue) 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_passworduser_pw, owner_passwordowner_pw, permissions_flagperm_code, algorithmAES-256 ) with open(output_path, wb) as f: writer.write(f)merge_page的overTrue表示水印覆蓋在正文之上配合 0.15 的透明度效果就是水印浮在文字上方但不影響閱讀。如果你希望水印在文字下方改成overFalse但要注意有些 PDF 的圖層順序會導致水印被完全遮住實測下來overTrue更穩(wěn)定。3.4 批量調(diào)度與日志批量處理用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(parentsTrue, exist_okTrue) 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(recipientrecipient) user_pw config[encryption][user_password_template].format(recipientrecipient) try: wm_path create_watermark_pdf(watermark_text, font_name, config[watermark], temp_wm.pdf) encrypt_pdf( str(pdf_file), str(output_dir / fsecure_{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 份文檔日志里會清楚記錄每一份的狀態(tài)。如果某一份因為字體缺失或 PDF 損壞失敗其他 99 份不受影響。4. 驗證請求與成功結(jié)果水印校驗與解密驗證批量跑完之后不能只看日志說成功就完事必須做兩步驗證水印是否真的蓋上去了加密是否真的生效了。4.1 水印校驗最直接的方法是用 pypdf 讀取輸出文件檢查頁面內(nèi)容流里是否包含水印文本。但更實用的是用命令行工具快速抽檢# 用 pdftotext 提取文本看水印文字是否出現(xiàn) pdftotext output_pdfs/secure_ClientA_Proposal.pdf - | grep CONFIDENTIAL如果輸出里有CONFIDENTIAL - ClientA ONLY說明水印文本已經(jīng)寫入。但文本提取只能驗證文字存在驗證不了透明度和旋轉(zhuǎn)。要驗證視覺效果建議用 Python 渲染第一頁為圖片from pdf2image import convert_from_path images convert_from_path(output_pdfs/secure_ClientA_Proposal.pdf, first_page1, last_page1) images[0].save(check_watermark.png)打開圖片你應該能看到 45 度傾斜的灰色半透明水印覆蓋在正文上方。如果水印太淡或太濃回去調(diào)transparency參數(shù)。4.2 解密驗證加密驗證要確認兩件事用正確密碼能打開用錯誤密碼打不開且權(quán)限限制生效。from pypdf import PdfReader # 正確密碼 reader PdfReader(output_pdfs/secure_ClientA_Proposal.pdf) if reader.is_encrypted: result reader.decrypt(Open_ClientA_2024) print(f解密結(jié)果: {result}) # 應該輸出 1 或 2表示成功 print(f頁數(shù): {len(reader.pages)}) # 錯誤密碼 reader2 PdfReader(output_pdfs/secure_ClientA_Proposal.pdf) if reader2.is_encrypted: result2 reader2.decrypt(WrongPassword) print(f錯誤密碼解密結(jié)果: {result2}) # 應該輸出 0表示失敗如果正確密碼返回 1 或 2錯誤密碼返回 0說明加密生效。權(quán)限驗證可以用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: fBearer {API_KEY}}, json{ model: gpt-4o-mini, messages: [ {role: system, content: 你是一個日志分析助手請?zhí)崛∈№椇驮?。}, {role: user, content: log_text} ] } ) return resp.json()[choices][0][message][content]這樣你不需要逐行翻日志模型會直接告訴你哪幾份失敗了、可能的原因是什么。注意 Base URL 用https://taotoken.net/api不要加 UTM 參數(shù)那是給網(wǎng)頁用的。5. 本篇常見錯排查401、local proxy failed、reading choices、OAuth批量跑 Skill 的時候報錯集中在幾個地方。我按真實遇到的順序列出來你對照排查。5.1 401 Unauthorized這是最常見的通常是 Key 沒讀到或 Key 無效。檢查順序.env文件是否在 Skill 運行目錄下load_dotenv()是否在讀取 Key 之前調(diào)用。環(huán)境變量名是否和代碼里一致比如代碼讀TAOTOKEN_API_KEY.env里寫的是TAOTOKEN_KEY那就讀不到。Key 是否被復制時帶了空格或換行建議用strip()處理。如果用的是 Coding Plan 的 Key確認該 Key 是否有權(quán)限調(diào)用你指定的模型。修復后重新跑一份文檔測試不要直接跑 100 份。5.2 local proxy failed這個報錯通常出現(xiàn)在你本地網(wǎng)絡環(huán)境有代理設(shè)置但 Skill 請求 TaoToken API 時走了代理導致連接失敗。檢查echo $HTTP_PROXY echo $HTTPS_PROXY如果有值在 Skill 里顯式禁用代理import os os.environ[HTTP_PROXY] os.environ[HTTPS_PROXY] 或者在 requests 調(diào)用時加proxies{http: None, https: None}。注意不要用任何非正規(guī)的網(wǎng)絡工具直接連 TaoToken 的 API 地址即可。5.3 reading choices 報錯這個報錯說明模型返回的 JSON 結(jié)構(gòu)里沒有choices字段通常是請求體格式不對或模型 ID 寫錯。檢查model字段是否拼寫正確比如gpt-4o-mini不要寫成gpt4o-mini。請求頭Content-Type是否為application/json。如果返回的是錯誤信息先打印resp.text看完整內(nèi)容再定位。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 相關(guān)報錯如果你在 Codex 里配置了 OAuth 方式的接入報錯通常是 token 過期或回調(diào)地址不匹配。檢查回調(diào)地址是否和控制臺里配置的一致包括端口號。token 是否過期重新走一次授權(quán)流程。如果同時配了 API Key 和 OAuth確認 Skill 實際用的是哪一種不要混用。對于批量文檔處理這種場景我建議直接用 API Key比 OAuth 簡單而且 Key 可以單獨限制權(quán)限審計更方便。5.5 三件套檢查清單如果你用的是 CC Switch、Cline MCP 或 Codex 的auth.json出現(xiàn)連接問題時按這三件套逐項核對配置項正確值常見錯誤Base URLhttps://taotoken.net/api寫成帶 UTM 的網(wǎng)頁地址API Keysk-開頭復制時漏字符或帶空格Model IDgpt-4o-mini等拼寫錯誤或用了不存在的模型auth.json的配置示例{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: gpt-4o-mini }三項都對上基本不會出現(xiàn)連接問題。如果還報錯先去模型對話頁發(fā)一條測試消息確認 Key 本身可用再排查 Skill 側(cè)。6. 語義一致 CTA把 Skill 接入你的文檔安全流程到這里你已經(jīng)有了一個能跑通 100 份文檔的防偽加密水印 Skill。接下來要做的是把它接入你日常的文檔分發(fā)流程。如果你在排障或接入階段遇到問題先去 API Keys 頁面確認 Key 狀態(tài)再對照接入文檔檢查 Base URL 和 Model ID。文檔里有完整的請求示例和錯誤碼說明比在代碼里猜要快。如果你想先驗證模型調(diào)用是否正常用模型對話頁發(fā)一條測試消息確認返回結(jié)構(gòu)里有choices字段再回到 Skill 里跑批量任務。如果你打算把這個 Skill 做成每天定時跑的長期任務比如每天早上 8 點自動處理前一天的待分發(fā)文檔建議用 Coding Plan 的額度Key 的權(quán)限可以單獨限制在文檔處理相關(guān)的模型上避免和其他業(yè)務混用。最后提醒一個實操細節(jié)批量跑之前先用 3 份文檔做小樣本測試確認水印位置、透明度、密碼規(guī)則都符合預期再放開到 100 份。我踩過的坑是第一次直接跑全量結(jié)果水印字體沒注冊成功100 份全變成方框只能刪掉重來。小樣本測試花 5 分鐘能省你半小時。