--- name: contract-rework-desensitize description: 合同审查返工任务处理+文件脱敏流程。从Nextcloud取返工文件,按事件分组处理,完成后对敏感信息进行脱敏(当事人名称、金额、个人信息),交付脱敏版本。 tags: [contract, rework, desensitize, docx, nextcloud] --- # 合同审查返工任务——处理与脱敏 ## 触发条件 - 收到返工任务记录文件(通常包含多个"事件") - 需要对已审查合同的修订文件进行脱敏处理 - Doro或其他指导人要求将返工文件脱敏后归档 ## 第一阶段:返工文件获取与确认 ### Step 1: 定位文件 1. 在Nextcloud的Doro目录下查找返工文件: - 主目录:`/var/www/html/data/doro/files/Doro合同审查任务/` - 子目录:`待审查/`、`任务交付/`、`参考文件/` - 也可能在:`/var/www/html/data/admin/files/小Maggie协作区/` 2. **必须精确匹配文件名**,"差不多"等于没找到 3. 找到后先报告:文件名、路径、修改时间,确认后再操作 ### Step 2: 复制到工作区 ```bash # 从Nextcloud容器复制到本地工作目录 docker cp nextcloud-aio-nextcloud:/var/www/html/data//files// /tmp/rework/ ``` ### Step 3: 确认返工内容 - 阅读返工任务记录,理解每个"事件"的返工要求 - 按事件分组整理对应的合同文件 - 确认哪些文件需要脱敏 ## 第二阶段:脱敏处理 ### 核心原则 - 使用**XML级别操作**(zipfile + lxml),不用python-docx(它会破坏修订标记) - 必须处理**修订标记内容**(w:ins / w:del 中的文本) - 需要**多轮扫描**,因为文本可能跨多个XML run被拆分 - 每轮替换后运行验证脚本确认 ### Step 4: 建立替换规则 根据合同内容,确定以下替换映射: | 类别 | 原始内容 | 替换为 | |------|---------|--------| | 甲方名称 | 具体单位名 | `甲方单位` | | 乙方名称 | 具体公司名 | `乙方单位` | | 个人姓名 | 法定代表人、联系人等 | `XXX` | | 金额 | 具体数字 | `XXXXXX` | | 地址 | 具体地址 | `XX路XX号` | | 电话 | 手机/座机 | `XXXXXXXXXXX` | | 银行账号 | 具体账号 | `XXXXXXXXXXXXXXX` | | 统一社会信用代码 | 具体代码 | `XXXXXXXXXXXXXXXXXX` | ### Step 5: 执行脱敏(Python脚本) ```python import zipfile, shutil, os, re, copy from lxml import etree from io import BytesIO NSMAP = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'} def desensitize_docx(input_path, output_path, replacements): """ replacements: list of (pattern_str_or_regex, replacement_str) """ with zipfile.ZipFile(input_path, 'r') as zin: with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zout: for item in zin.infolist(): data = zin.read(item.filename) if item.filename in ('word/document.xml', 'word/header1.xml', 'word/header2.xml', 'word/footer1.xml', 'word/footer2.xml', 'word/comments.xml'): data = desensitize_xml(data, replacements) zout.writestr(item, data) def desensitize_xml(xml_bytes, replacements): tree = etree.fromstring(xml_bytes) # 1) 逐个w:t节点直接替换 for t_node in tree.iter('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}t'): if t_node.text: for pattern, repl in replacements: t_node.text = t_node.text.replace(pattern, repl) # 2) 处理跨run拆分的情况:拼接同一段落/ins/del内所有w:t的文本, # 检查拼接后是否包含敏感词,如果是则在首个匹配run中替换,清空后续run for parent in tree.iter(): runs = parent.findall('.//w:r', NSMAP) if len(runs) < 2: continue full_text = '' t_nodes = [] for r in runs: for t in r.findall('.//w:t', NSMAP): if t.text: full_text += t.text t_nodes.append(t) if not full_text: continue for pattern, repl in replacements: if pattern in full_text: # 重建:把替换后的文本放在第一个t_node,清空其余 full_text = full_text.replace(pattern, repl) if t_nodes: t_nodes[0].text = full_text for tn in t_nodes[1:]: tn.text = '' return etree.tostring(tree, xml_declaration=True, encoding='UTF-8', standalone=True) ``` ### Step 6: 验证脱敏结果 ```python def verify_desensitized(docx_path, sensitive_terms): """检查脱敏后文件是否还有残留敏感词""" found = [] with zipfile.ZipFile(docx_path, 'r') as z: for fname in z.namelist(): if fname.endswith('.xml'): content = z.read(fname).decode('utf-8', errors='ignore') for term in sensitive_terms: if term in content: found.append((fname, term)) return found # 空列表 = 全部清除 ``` **关键**:如果验证发现残留,分析原因(通常是跨run拆分或变体写法),补充替换规则后重新执行。 ## 第三阶段:交付 ### Step 7: 上传到Nextcloud ```bash # 目标目录(按实际需求选择) TARGET_DIR="/var/www/html/data/doro/files/Doro合同审查任务/任务交付/" # 或 TARGET_DIR="/var/www/html/data/admin/files/小Maggie协作区/返工任务记录_YYYYMMDD/" # 复制文件 docker cp /tmp/rework/脱敏后文件.docx nextcloud-aio-nextcloud:$TARGET_DIR # 修正权限 docker exec nextcloud-aio-nextcloud chown www-data:www-data "$TARGET_DIR/脱敏后文件.docx" # 扫描文件系统 docker exec -u www-data nextcloud-aio-nextcloud php occ files:scan ``` ### Step 8: 打包(如需要) ```bash # 在容器内或本地打ZIP cd /tmp/rework && zip -r 返工任务记录_脱敏版_YYYYMMDD.zip *.docx ``` ### Step 9: 通知 - 企微群通知完成情况 - 说明脱敏了哪些文件、替换了哪些类别的信息 ## ⚠️ Pitfalls 1. **不要用python-docx处理带修订标记的文件**——python-docx会丢失/破坏tracked changes 2. **文本跨run拆分是常见问题**——Word经常把一个词拆到多个``节点中,简单的逐节点替换会漏掉 3. **变体写法**——同一公司名可能出现简称、全称、甚至错别字版本,需要把所有变体都加入替换列表 4. **header/footer/comments也要处理**——不只是document.xml 5. **替换顺序**——长字符串先替换,避免短字符串先匹配导致长字符串被部分替换后无法匹配 6. **文件名不改**——遵循工作原则,不擅自修改文件名 7. **每次脱敏后必须验证**——运行verify脚本,确认零残留后才能交付