OCR 与素材资产管理工程手册
从一张“已经识别”的扫描件说起
产品团队收到一批历史合同截图,开发者用 OCR 导出文本,搜索很快能查到“续费金额”。几周后,法务发现一份结果把 8 识别成了 3,表格第二列又被拼到第一列末尾。与此同时,设计团队把供应商图片直接放进 Git 仓库:文件名是 最终版2.png,没有作者、授权范围和原始下载地址,压缩图覆盖了原图,没人知道哪些页面正在使用它。
这两个事故看似无关,实际都缺少同一种能力:二进制输入、机器派生结果、人工判断和使用权利之间没有可追踪关系。OCR 输出不是事实源,只是带模型、参数和置信度的派生资产;图片也不只是文件,它还带来源、作者、许可证、使用范围、敏感等级、保留期和下游引用。
一条可靠链路应当能回答:原始文件是否被改过,识别时用了哪个引擎和语言包,低置信词位于哪里,谁复核了关键字段,派生图从哪个原图生成,发布目标是否在授权范围内,以及删除一个对象会影响哪些页面和备份。
先决定文字在哪里被看见
本机 OCR 和云 OCR 的差别不只是“准确率谁高”。文字从图片进入引擎之前,架构师先画清数据路径:输入从哪里来,在哪个区域处理,是否落盘,结果保存多久,谁能读取,日志会留下什么,失败任务怎样清理。
| 路径 | 适合的开发现场 | 主要代价 | 关键判断 |
|---|---|---|---|
| 本机 Tesseract | 源文件敏感、可离线、印刷体版式较稳定、需要批量自动化 | 自己承担预处理、版面分析、语言模型和算力 | 工作站能否承载峰值,语言和版式是否可控 |
| 内网 OCR 服务 | 多团队共享、数据不能出网、需要统一版本和审计 | 服务部署、队列、容量、升级和模型治理 | 是否有明确 owner 与隔离边界 |
| 云 OCR | 手写、复杂票据、版面结构或弹性吞吐要求较高 | 网络、调用成本、供应商权限与数据边界 | 区域、留存、训练用途、删除 API 和退出格式是否满足制度 |
| 人机结合 | 金额、证件号、合同条款等错误代价高 | 复核队列与人员成本 | 哪些字段必须双人复核,如何保存证据 |
“文件上传后接口返回成功”不能作为准入结论。云 OCR 的处理、留存、训练和日志语义必须按具体服务与调用模式核对,不能互相外推:
| 服务与模式 | 官方文档明确写出的数据处理事实 | 接入时仍要固化的字段 |
|---|---|---|
| Google Cloud Document AI 在线请求 | 请求中的文档在内存中处理,不持久化到磁盘;请求的部分元数据会被临时记录。Google 说明不会用客户数据训练 Document AI 模型。 | 处理器位置、API 模式、请求元数据、输出接收端和组织合同 |
| Google Cloud Document AI 批处理 | 为完成分析和返回结果,文档会以加密的临时形式保存;通常处理后立即删除,异常中止时有最长一天的故障保护 TTL。 | 输入/输出 URI、区域、多页和大小限制、异常任务清理与重试 |
| Azure Document Intelligence 分析 | 输入和结果在资源所在区域加密后临时存储;分析完成后的结果默认保存 24 小时,也可以调用 Delete Analyze Result API 提前清除。 | 资源区域、同步或异步接口、结果删除响应、客户训练数据所在存储 |
| Amazon Textract | AWS 采用责任共担模型;调用方负责凭证、IAM、加密、CloudTrail 和其配置的存储。敏感内容不应放进可能进入诊断日志的自由文本字段;Custom Queries 的训练内容有单独的区域和删除语义。 | API 类型、S3 输入/输出位置及生命周期、日志字段、IAM 权限、区域和退出动作 |
上述事实分别来自 Google Cloud Document AI 安全说明、Azure Document Intelligence 数据隐私说明 和 Amazon Textract 数据保护说明,只适用于文档所列的服务、区域和模式。产品能力、套餐和组织合同可能另外施加限制,接入前应读取目标 API 的数据处理条款并用真实租户验证删除、日志和导出行为。
高敏文件优先在受控本机或内网运行。选择云端时,至少把 provider、region、processor、同步或异步模式、输入桶、输出桶、客户管理密钥(若该模式支持)、结果删除动作和请求审计 ID 写进任务记录。API Key 不得出现在命令历史、URL、素材元数据或仓库;运行身份只获得读取指定输入前缀、写入指定结果前缀和调用指定处理器的权限。
采集入口不能直接连到 OCR 或素材库。原始文件先进入隔离区完成哈希、来源、授权和敏感分类,OCR 输出再继承父资产等级;只有脱敏证据、识别质量和 manifest 同时过关,派生物才能进入对应权限层:
图中有三道不能被“识别成功”跳过的边界。采集边界决定文件是否有权被处理,计算边界决定原文能否交给外部引擎,入库边界决定原图、OCR 文本和脱敏派生物分别由谁可见。脱敏不会改写原图或机器原始输出,而是产生带父节点、参数、坐标变换和复核证据的新资产;因此后续发现漏脱敏时,可以沿派生链撤下索引与发布版本,而不破坏原始审计证据。
安装 Tesseract 和语言数据
Tesseract 引擎采用 Apache-2.0 许可,没有内置 GUI;语言数据和其他模型文件是独立部件,许可证和版本不能从引擎许可证推断。只装可执行文件并不意味着能识别简体中文。当前稳定主线是 5.x,具体安装入口与平台差异以 Tesseract 安装文档为准。
在 Debian 或 Ubuntu 开发机上安装引擎、英文、简体中文和方向检测数据:
sudo apt update
sudo apt install tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim
tesseract --version
tesseract --list-langs--list-langs 的输出至少应包含 eng 与 chi_sim;需要自动方向检测时还要有 osd。发行版实际打包了哪一组 .traineddata 不能靠包名猜测,应记录语言文件路径、版本、来源和 SHA-256。官方维护的 tessdata_fast、tessdata 与 tessdata_best 说明显示:best 通常更慢但可能更准,fast 使用更小的整数化网络,而 tessdata 还包含 Legacy 与 LSTM 所需的兼容数据。不要只凭仓库名称替换生产模型,应在自己的扫描件基准集上比较字段准确率、延迟和内存。
macOS 可以通过 Homebrew 安装引擎,再按 brew info tesseract 显示的目录确认语言数据位置:
brew install tesseract
brew info tesseract
tesseract --list-langsWindows 原生二进制由官方安装页链接到外部构建,不是 Tesseract 项目自身发布的安装器。团队若无法承担第三方二进制来源与更新审查,可在 WSL 的 Ubuntu 环境复用上述安装链路。必须使用原生安装器时,固定下载入口和文件哈希,验证签名或发布者,把安装目录加入 PATH,并将额外语言文件放入安装实例的 tessdata 目录。
自带语言数据时,不要改机器全局目录。把模型作为受控依赖放到工具缓存,并显式传入目录:
mkdir -p .tools/tessdata
# 由依赖下载任务写入 eng.traineddata、chi_sim.traineddata、osd.traineddata
tesseract --tessdata-dir ./.tools/tessdata \
fixtures/ocr/clean.png out/clean \
-l chi_sim+eng --oem 1 --psm 6--tessdata-dir 指向包含 .traineddata 的目录;-l 决定语言及顺序;--oem 1 选择 LSTM 引擎;--psm 6 假设图像是一个统一文本块。Legacy 引擎使用 --oem 0,同时使用两种引擎的 --oem 2 要求语言数据同时包含两套模型;--oem 3 只是自动选择,不是缺少模型时的兼容补丁。官方说明中 tessdata_fast 和 tessdata_best 不含 Legacy 模型,因此不能对它们使用依赖 Legacy 的模式。默认 --psm 3 是不带 OSD 的完整页面自动分割,单行可用 7,稀疏文字可用 11;--psm 0 只做方向和文字脚本检测,不输出 OCR 文本。参数不是“越智能越好”:错误的页面分割假设会让字符模型还没开始识别,文字块就已经被切错。完整模式含义见 Tesseract 命令行文档。
模型文件要和应用依赖一样锁定来源、SHA-256、许可证和变更流程。升级时同时记录引擎版本、模型集合、预处理版本和基准集结果,否则“准确率变了”无法定位是二值化、分割还是模型导致。
建立第一个可复核的识别实验
准备一个没有真实客户信息的测试图 fixtures/ocr/clean-eng.png,白底黑字,内容固定为两行。下面的实验命令按 POSIX shell 编写,Windows 可在 WSL 中运行:
Order ID A-1042
Total 128.50若已安装 ImageMagick,可生成同样的输入;字体名按开发机实际安装字体替换:
mkdir -p fixtures/ocr out/ocr
magick -size 1600x500 xc:white \
-font DejaVu-Sans -pointsize 72 -fill black -gravity center \
-annotate +0+0 $'Order ID A-1042\nTotal 128.50' \
fixtures/ocr/clean-eng.png
tesseract fixtures/ocr/clean-eng.png out/ocr/clean-eng \
-l eng --oem 1 --psm 6 txt tsv
cat out/ocr/clean-eng.txt正向结果应包含完整订单号和金额,且 out/ocr/clean-eng.tsv 中词级记录的 level 为 5,具有 left、top、width、height、conf 和 text。页面、块、段落和行级记录也会出现在 TSV 中,它们的 conf 可能是 -1,所以不能把所有行都当作词来计算阈值。纯文本只能证明“有字符”,TSV 才保留词位于页面哪里以及引擎给出的置信度;格式示例可对照 官方 TSV 输出说明。
下面的 Node 脚本把低置信词转成可进入复核队列的证据。阈值 85 只是演示值,生产阈值要由标注集和字段风险校准:
// scripts/check-ocr-confidence.mjs
import fs from "node:fs";
const [file, thresholdText = "85"] = process.argv.slice(2);
const threshold = Number(thresholdText);
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 100) {
throw new Error("threshold must be a number from 0 to 100");
}
const content = fs.readFileSync(file, "utf8").replace(/\r\n?/g, "\n").trimEnd();
if (!content) throw new Error("empty TSV");
const [header, ...rows] = content.split("\n");
const names = header.split("\t");
const index = Object.fromEntries(names.map((name, i) => [name, i]));
for (const name of ["level", "left", "top", "width", "height", "conf", "text"]) {
if (index[name] === undefined) throw new Error(`missing TSV column: ${name}`);
}
const words = rows
.map((row) => row.split("\t"))
.filter((row) => row[index.level] === "5" && row[index.text]?.trim())
.map((row) => ({
text: row[index.text],
confidence: Number(row[index.conf]),
box: ["left", "top", "width", "height"].map((key) => Number(row[index[key]])),
}));
const lowConfidence = words.filter(
(word) => !Number.isFinite(word.confidence) || word.confidence < 0 || word.confidence < threshold,
);
console.log(JSON.stringify({ words: words.length, lowConfidence }, null, 2));
process.exitCode = lowConfidence.length ? 2 : 0;node scripts/check-ocr-confidence.mjs out/ocr/clean-eng.tsv 85在固定引擎、语言文件、字体和输入版本下,若该样本通过已校准的演示阈值,才预期退出 0 并输出 lowConfidence: []。如果某个词低于阈值、置信度不是数值或出现不适用于词级记录的负值,脚本退出 2,JSON 中的边界框可用于在原图上高亮复核。置信度不是“正确概率”,不同模型、语言、版式的分布也不相同;它只能作为风险排序信号,不能替代字段级真值比较。
反向实验要故意破坏版面
以下命令使用 POSIX shell,Windows 可在 WSL 中运行。把干净样本缩小、旋转并加噪,保留坏输入而不是手工修正后假装一次成功:
magick fixtures/ocr/clean-eng.png \
-resize 28% -rotate 8 -attenuate 0.35 +noise Gaussian \
fixtures/ocr/degraded-eng.png
set +e
tesseract fixtures/ocr/degraded-eng.png out/ocr/degraded-eng \
-l eng --oem 1 --psm 6 txt tsv 2>out/ocr/degraded-eng.stderr
ocr_exit=$?
node scripts/check-ocr-confidence.mjs out/ocr/degraded-eng.tsv 85
confidence_exit=$?
set -e
printf 'ocr_exit=%s confidence_exit=%s\n' "$ocr_exit" "$confidence_exit"
cat out/ocr/degraded-eng.txt
cat out/ocr/degraded-eng.stderr可接受的反向证据不是“命令一定报错”。OCR 进程很可能仍退出 0,但文本出现错字、丢词或 TSV 低置信列表非空,检查脚本退出 2。这正是最危险的失败类型:系统层成功,业务语义已经错误。退化参数、人工真值和预期错误应固定进测试数据版本;不能为了得到失败结果反复调阈值或破坏图像,之后却把这种人为构造当成生产质量结论。
再做一个页面分割反例:对同一张双栏或表格图片分别使用 --psm 6 与 --psm 3,比较 TSV 文件顺序、page_num、block_num、par_num、line_num、word_num 和坐标,而不是只看 block_num。预期错误模式会把跨列内容拼成一行,或把单元格顺序打乱。Tesseract 官方质量指南明确指出倾斜会显著影响行分割,表格通常需要额外的布局分析;因此不能用整页平均字符准确率掩盖金额列错位。
实验结束后只删除可再生输出:
rm -rf out/ocr固定测试图、人工真值和模型哈希要保留。没有真值,升级后的“识别更多字符”可能只是生成了更多错误字符。
中文、旋转、低清图和表格需要分流
中文识别先确认语言包,而不是先调阈值:
tesseract --list-langs | grep -E 'chi_sim|eng|osd'
tesseract fixtures/ocr/clean-zh.png out/ocr/clean-zh \
-l chi_sim+eng --oem 1 --psm 6 txt tsv中英混排时,语言顺序可能影响速度和结果,要用实际资料比较 chi_sim+eng 与 eng+chi_sim。业务词典、产品名和编码可以进入用户词表,但词表会提高“看起来像业务词”的偏好;错误词表同样会稳定制造错字,必须版本化并有反例。
Tesseract 自身已经会做一部分图像处理;官方质量指南把放大、二值化、去噪、形态学、旋转/去倾斜和边框都列为针对性手段,而不是一条必经流水线。预处理要按症状分支,不要把锐化、二值化、放大全部叠在每张图上;250%、-despeckle 和自适应阈值都只是候选参数,可能抹掉标点、细笔画、印章或水印:
# 低清灰度文本:放大、灰度化、轻量去噪,再用自适应阈值形成候选图
magick input.png -resize 250% -colorspace Gray \
-despeckle -adaptive-threshold 25x25+10% out/preprocessed.png
# 已知旋转方向:先校正,再保留白边,避免紧裁切破坏分割
magick input.png -rotate -90 -bordercolor white -border 20 out/rotated.png
tesseract out/preprocessed.png out/result -l chi_sim+eng --psm 6 tsv txt同一原图可生成多个候选派生图,但门禁必须比较原图和派生图:字符召回是否提高,数字、标点和专有名词是否退化,边界框是否仍映射到正确位置。过度侵蚀会吃掉细笔画,过度膨胀会粘连字符,自适应阈值会把浅色印章或水印当文字。原图永远只读保存,预处理参数写入派生关系,不能覆盖输入。凡是缩放、裁切、旋转、透视校正或去倾斜,都要同时保存输入尺寸、输出尺寸、裁切偏移和可逆的坐标变换;复核界面应把 TSV 坐标转换回原图后再定位,不能直接把派生图坐标当成原图坐标。
方向检测可用 osd,但短文本和稀疏文本可能没有足够证据:
tesseract fixtures/ocr/page.png stdout -l osd --psm 0 2>out/ocr/osd.stderr--psm 0 的输出只有方向和脚本检测结果,osd.traineddata 也不是中文 OCR 语言包。需要在一次调用中结合方向检测和识别时,可测试 --psm 1(自动页面分割并带 OSD)或 --psm 12(稀疏文字并带 OSD),但仍要用基准集验证。低置信或版面很短时,按来源元数据、图像长宽、人工采样决定旋转;不要让错误方向检测把整批文件旋转错。
表格是另一类对象。Tesseract 擅长识别字符,不负责可靠恢复跨行、合并单元格、表头层级和业务字段。可先用版面工具检测表格区域和单元格,再对每个区域 OCR,并将 row、column、坐标和原图页码一起保存。金额、税号、日期、数量等高风险字段还要执行类型校验、合计校验和人工复核。一个总金额与分项之和不等的结果,即使每个词置信度都高,也必须拒绝入库。
让人工复核成为状态机,而不是聊天确认
OCR 任务至少经历以下状态:
ingested -> preprocessed -> recognized -> needs_review -> accepted
| |
v v
rejected published每次转换都保存操作者或服务身份、输入哈希、输出哈希、引擎与模型、参数、理由和证据。机器门禁可以先按字段风险分流:
普通全文检索允许低风险文本自动进入“候选索引”,搜索结果明确标记为机器识别。金额、账号、合同条款、证件号等关键字段必须与人工真值或业务规则比较。低置信词、阅读顺序冲突、类型校验失败、合计不平、敏感信息命中进入复核队列。
复核者看到原图局部、机器结果、坐标和上下文,不能只看脱离原图的一行文本。高风险用途采用双人复核或复核加抽样审计;复核者不能覆盖机器原始输出,只能产生新版本。
衡量质量时,不用单一“准确率”。至少分开观察字符错误率、词错误率、关键字段完全匹配率、表格单元格定位正确率、自动通过率、人工退回率、复核等待年龄和复核后返工率。阈值来自按来源、语言、版式和风险分层的标注集;模型升级只有在关键字段不退化、错误可被现有门禁捕获、处理成本可接受时才放行。
素材不是文件夹,而是带权利的资产图
建议把素材分成四层:
source 原始下载、拍摄或设计导出,不可变
working 可编辑工程文件和处理中间件
derived 压缩、裁切、转码、加水印、OCR 等可再生结果
published 实际进入网站、应用、文档或 CDN 的版本代码仓库只保存体积可控、需要随代码原子发布的 published 资产和小型测试 fixture。高分辨率原图、PSD、录屏、批量扫描件及 OCR 中间结果进入有版本、加密、生命周期和访问日志的对象存储。仓库里保存稳定 asset ID、manifest 和构建所需派生图;不要把对象存储 URL 当永久身份,因为桶、区域、CDN 和供应商都会变。
命名服务于人类浏览,身份服务于机器。推荐文件名表达主题、用途、语言和变体,例如:
checkout-empty-state.zh-CN.desktop.v03.webp
architecture-cache-failover.en-US.diagram.v02.svg不要把作者姓名、客户名、账号、手机号或未发布项目代号塞进文件名。稳定身份使用 asset_id,文件字节身份使用 SHA-256。文件改名不改变文件字节哈希;像素或嵌入文件的元数据变化通常会改变哈希,而外置 manifest 元数据变化不会改变它。这两种身份不能互相替代。
用 manifest 连接来源、识别结果和发布用途
每个资产保存一条结构化记录。下面的 YAML 展示最小但可扩展的字段:
schema_version: 1
asset_id: ast_01HXEXAMPLE0000000000000
logical_name: checkout-empty-state
media_type: image/png
hash_algorithm: sha256
content_sha256: 4f7d9a6d5d4b5f8c0d0e00000000000000000000000000000000000000000000
manifest_sha256: 9a4c2c0000000000000000000000000000000000000000000000000000000000
byte_size: 1842331
dimensions: { width: 2400, height: 1500 }
source:
kind: commissioned
uri: contract://design-vendor/asset-042
author: Example Studio
acquired_via: procurement-ticket-EXAMPLE-42
rights:
license_expression: LicenseRef-Example-Brand-Asset
license_text_ref: evidence://rights/EXAMPLE-42/license-text
grant_evidence: evidence://rights/EXAMPLE-42
attribution_required: false
attribution_text: null
allowed_channels: [web, mobile-app]
allowed_regions: [global]
commercial_use: true
modification_allowed: true
rights_review_status: approved
expires_at: null
classification:
pii_status: none
pii_categories: []
pii_inherited_from: []
redaction_status: not_required
confidentiality: internal
moderation_status: approved
lifecycle:
owner: team-content-platform
retention_class: active-plus-archive
deletion_hold: false
review_checkpoint: release-cycle
derivation:
parents: []
operation: original
tool: null
tool_version: null
code_revision: null
parameters: null
coordinate_transform: null
usage:
- target: repo://web/src/assets/checkout-empty-state.webp
published_asset_id: ast_01HXEXAMPLE0000000000000
content_sha256: 4f7d9a6d5d4b5f8c0d0e00000000000000000000000000000000000000000000
environment: production公开许可证字段应存 SPDX license expression,而不是把单个字符串都叫作 license_id;表达式可以使用 SPDX License List 的标准标识以及 AND、OR、WITH,不在列表中的内部合同或购买授权才使用 LicenseRef-...。LicenseRef 只是 SPDX 文档内的本地引用,必须在同一 manifest 包或 SPDX 文档中绑定对应的许可证文本;受控证据 URI 只能证明授予事实,不能替代许可证条款。allowed_channels、地区、期限、商业用途和修改权限是本地权利策略,不能假装都是 SPDX 许可证字段。
Creative Commons 的六种版权许可证都要求署名(除非权利人明确放弃或没有提供署名信息);NC 禁止商业用途,ND 禁止分享改编作品,SA 要求改编作品按相同条款分享。官方说明还要求核对具体版本和法律文本。“网上搜到”“可免费下载”“买过会员”都不是许可证。压缩、裁切、加水印、OCR 和翻译等操作是否构成需要额外许可的改编,应由权利审查决定,不能仅凭 modification_allowed: true 自动放行。授权记录要拆开作者、版权方、授权给谁、渠道、地区、期限、是否商业、是否允许修改和是否要求署名。
OCR 结果也是派生资产:单一来源时 parents 中记录扫描原图的 asset ID 和 content_sha256,多页、多图拼接或版面分析使用多个带 role 的父节点;operation 写 ocr,参数保存语言顺序、每个 .traineddata 的哈希、PSM、OEM、预处理 asset ID、引擎版本、模型哈希和坐标变换。文本、TSV、人工复核版本和搜索索引各有自己的内容哈希。manifest_sha256 应对去除自身哈希字段后的规范化 manifest 计算,避免同一资产因 YAML 排版差异产生不同身份。这样才能证明搜索索引来自哪一页,也能在模型升级后只重建受影响结果。
哈希、重复检测和检索各解决不同问题
SHA-256 只能找到字节完全一致的文件。相同照片经过重新编码、裁切或去元数据后哈希会变化,因此重复检测分三层:
入库先算加密哈希,拒绝完全重复字节。图片计算感知哈希或视觉向量,找“看起来相同”的候选。人工确认候选是同一资产、授权不同的副本,还是允许并存的派生版本。
感知哈希距离和向量相似度只能召回候选,不能自动合并。两张外观相同的图可能来自不同作者或合同,删除其中一条授权记录会破坏审计。确认重复后保留规范 asset ID,建立 alias,迁移引用,再按保留规则删除冗余二进制。
检索索引至少包含逻辑名称、标签、作者、来源、许可证、允许渠道、敏感等级、owner、OCR 文本、创建工具、尺寸和使用目标。搜索返回必须先经过权限过滤;不能因为 OCR 建了全文索引,就让原本只能访问单个合同的人通过搜索片段看到其他合同内容。高敏 OCR 文本可以只索引脱敏字段或不可逆关键词标记,原文仍由源系统鉴权。
索引是派生数据,可以重建。权利证据、manifest、原始文件和引用关系是事实记录,必须有备份、版本和完整性校验。搜索结果删除后还要清理缓存、缩略图、向量索引、OCR 文本、TSV 和可能包含原文的日志或临时文件,不能只删对象存储里的原图。
项目接入:从入库到构建门禁
一个可维护的项目结构可以是:
assets/
manifests/
published/
fixtures/
scripts/
asset-ingest.mjs
asset-verify.mjs
asset-usage-scan.mjs
.env.exampleasset-ingest 负责计算哈希、读取尺寸、分配 asset ID、保存原始对象并生成待补全 manifest;它不能默认授予发布权限。asset-verify 在合并和发布前检查:二进制哈希匹配,许可证字段完整,当前渠道与地区被允许,授权未失效,PII 已处理,派生链可追溯,owner 有效。asset-usage-scan 从代码、Markdown、设计导出和 CMS 中提取引用,更新使用图,但不静默删除“零引用”资产。
构建门禁的失败信息要能执行,例如:
ASSET_RIGHTS_CHANNEL_DENIED
asset_id=ast_01HXEXAMPLE0000000000000
requested_channel=advertising
allowed_channels=web,mobile-app
evidence=evidence://rights/EXAMPLE-42这比“图片不合规”更容易修复。缺许可证是阻断,哈希不一致是阻断,作者待补全也应阻断公开发布;缩略图暂未生成可以重试,但不能让构建偷偷改用原图。门禁读取的是版本化 manifest,不能在 CI 中临时调用个人网盘或依赖某位设计师的桌面登录态。发布产物应同时记录已验证的 manifest 版本和 published 资产哈希;回滚时切换到上一份仍通过权利、PII 和引用检查的 manifest,不应只把 CDN URL 改回旧字符串,也不能自动回滚到已删除、授权失效或处于保留锁下的原图。
清理测试接入时,先撤销发布引用,再删除派生文件与索引,最后根据保留规则处理原始对象和 manifest。用于证明授权、删除、复核和发布的记录通常不能与二进制同时立即消失;具体保留时间由法律、合同和内部制度决定,manifest 用保留类别表达,不写一个适用于所有资产的固定天数。若备份或不可变存储不能即时改写,应写入按 asset_id 生效的删除墓碑,并让恢复流程在重新提供服务前重放墓碑;在所有受保留策略约束的副本失效前,不能宣称已经完成物理删除。
PII、权限与删除必须沿派生链传播
截图和扫描件可能包含姓名、头像、地址、证件号、客户订单、访问令牌、浏览器标签和地理信息。入库前先分类;pii_status: none 只能表示已经完成检查,不能作为默认值。父资产含有 PII 时,子图、OCR 文本、TSV 坐标、缩略图、向量、缓存和日志默认继承高敏等级,除非有独立的脱敏证据。必要时在受控环境生成脱敏派生图和脱敏文本,原图权限比脱敏图更窄。EXIF 等元数据也可能带设备和位置,公开派生物应显式检查,而不是假设压缩工具会删除。
权限至少分开:上传原始素材、读取高敏原图、修改权利记录、批准公开发布、删除对象、管理保留锁和读取审计日志。素材作者不能仅凭上传权限把自己的记录标成“全球永久商业授权”;发布者也不能修改合同证据让门禁变绿。机器身份按桶前缀和动作授权,使用短期凭证,密钥进入秘密管理服务而不是 .env 提交。
删除请求到来时,从 asset ID 查父子关系、别名、发布目标、CDN、搜索索引、OCR 文本、TSV、缩略图、向量、缓存、备份、日志和法务保留。若处于合法保留状态,系统应拒绝物理删除并记录理由;否则按“停止新使用 -> 撤下发布 -> 清索引与缓存 -> 删除 PII 派生物 -> 删除或隔离原始对象 -> 生成删除证明”的顺序执行。任何一步失败都保留状态和重试入口,不能返回一个笼统成功。对最终一致的 CDN、索引和备份,要记录每层的确认时间、残留窗口和后续检查结果。
反向演练可以给某测试资产建立两级派生图、OCR 文本和一个发布引用,然后发起删除。预期门禁先拒绝仍被引用的删除;撤下引用后,任务逐项清理并留下每个存储层的对象 ID 与结果。若搜索仍能查到文本、CDN 仍返回 200、派生图仍可访问,删除就没有完成。
容量和成本从派生倍数开始算
素材容量不是原文件总和。一个源图可能产生编辑文件、多个尺寸、WebP/AVIF、缩略图、OCR TSV、文本、向量、CDN 缓存和备份。容量模型至少包含:
monthly_bytes = source_ingest
+ working_versions
+ derived_variants
+ ocr_and_index
+ replication
+ backup持续观察每个 asset 的平均派生数、无引用资产字节、重复候选字节、各存储层增长率、OCR 每页耗时、队列等待年龄、失败重试次数、人工复核分钟数和出口流量。云 OCR 成本不能只看“每页单价”,还要算对象存储、跨区域传输、重试、版面模型、人工复核和退出导出;本机方案也有工作站算力、队列等待、升级、监控和误识别返工成本。
设置预算时优先用趋势和不变量:派生数不应在流水线重跑后单调增长;同一源哈希的可再生结果应命中缓存;删除完成后活跃存储和索引不再保留对象;无引用资产必须有 owner、保留理由或清理任务。演示阈值不能直接复制到生产,实际告警线由增长基线、存储预算和恢复目标决定。
供应商退出和长期治理
云 OCR、素材库或设计平台准入时就演练退出。导出不仅要拿到二进制,还要包括原始文件、版本、manifest、作者与授权证据、标签、评论、OCR 坐标和置信度、派生关系、使用引用、审计日志及旧 ID。给旧 ID 建映射,抽样验证哈希和权利字段,再把旧平台设为只读;只下载“当前最终图”会丢掉授权与来源链。
OCR 供应商切换时保留一组固定基准集,用新旧引擎同时运行,比较关键字段、版面顺序、置信度分布、延迟和人工复核量。不同厂商的置信度尺度不能直接比较,迁移门禁以真值和业务错误成本为准。切换完成后撤销 API 身份、删除云端处理器或临时结果、清理输入输出桶,并保存可审计的删除结果。
团队至少明确四类 owner:OCR 运行 owner 负责引擎、模型、队列和故障;数据 owner 决定可处理范围与复核等级;素材 owner 维护用途和下游引用;权利 owner 审核许可证、合同和退出。一个人可以兼任,但职责不能消失。
长期巡检应能完成以下闭环:从发布页面追到 published 资产、派生图、原图和授权证据;从 OCR 搜索结果追到页码、坐标、模型、预处理和人工复核;从删除请求查到所有副本和索引;从模型或供应商升级看到基准集差异和回滚入口。只有这些关系都能双向走通,OCR 才是可验证的数据生产线,素材库才是可治理的工程资产,而不是另一个更大的共享文件夹。
