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