diff --git a/services/document_service.py b/services/document_service.py index 2234573..60ed11d 100644 --- a/services/document_service.py +++ b/services/document_service.py @@ -217,17 +217,28 @@ class DocumentService: if not full_text: return - # 检查是否有占位符需要替换 + # 检查是否有占位符需要替换(使用多种方式检查) has_placeholder = False + found_placeholders = [] for field_code in field_data.keys(): placeholder = f"{{{{{field_code}}}}}" if placeholder in full_text: has_placeholder = True - break + found_placeholders.append(placeholder) + else: + # 尝试使用正则表达式检查(处理可能的编码问题) + import re + if re.search(re.escape(placeholder), full_text): + has_placeholder = True + found_placeholders.append(placeholder) if not has_placeholder: return + # 调试信息:记录找到的占位符 + if found_placeholders: + print(f"[DEBUG] 段落中发现占位符: {found_placeholders}, 段落文本前50字符: '{full_text[:50]}'") + # 收集所有runs及其位置和格式信息 runs_info = [] current_pos = 0 @@ -269,11 +280,27 @@ class DocumentService: for field_code, field_value in field_data.items(): placeholder = f"{{{{{field_code}}}}}" replacement_value = str(field_value) if field_value else '' - # 替换所有出现的占位符 - while placeholder in final_text: - final_text = final_text.replace(placeholder, replacement_value, 1) - replacement_count += 1 - print(f"[DEBUG] 替换占位符: {placeholder} -> '{replacement_value}'") + + # 检查占位符是否在文本中(使用多种方式检查,确保兼容性) + if placeholder in final_text: + # 替换所有出现的占位符 + before_replace = final_text + final_text = final_text.replace(placeholder, replacement_value) + count = before_replace.count(placeholder) + replacement_count += count + if count > 0: + print(f"[DEBUG] 替换占位符: {placeholder} -> '{replacement_value}' (共 {count} 次)") + else: + # 尝试使用正则表达式匹配(处理可能的编码或格式问题) + import re + escaped_placeholder = re.escape(placeholder) + if re.search(escaped_placeholder, final_text): + before_replace = final_text + final_text = re.sub(escaped_placeholder, replacement_value, final_text) + count = len(re.findall(escaped_placeholder, before_replace)) + replacement_count += count + if count > 0: + print(f"[DEBUG] 使用正则表达式替换占位符: {placeholder} -> '{replacement_value}' (共 {count} 次)") # 找到包含占位符的第一个run,使用它的格式 placeholder_run_format = None @@ -295,6 +322,9 @@ class DocumentService: # 如果只有一个run,直接替换文本(会自动保持格式) if len(runs_info) == 1: runs_info[0]['run'].text = final_text + # 验证替换是否成功 + if runs_info[0]['run'].text != final_text: + print(f"[WARN] 单run替换后验证失败:期望 '{final_text[:50]}...',实际 '{runs_info[0]['run'].text[:50]}...'") else: # 多个run的情况:合并为一个run,保持格式 # 先清空所有runs @@ -305,6 +335,14 @@ class DocumentService: first_run = runs_info[0]['run'] first_run.text = final_text + # 验证文本是否被正确写入 + if first_run.text != final_text: + print(f"[WARN] 多run替换后验证失败:期望 '{final_text[:50]}...',实际 '{first_run.text[:50]}...'") + # 尝试再次写入 + first_run.text = final_text + if first_run.text != final_text: + print(f"[ERROR] 多次尝试写入失败,可能存在严重问题") + # 应用格式(使用包含占位符的run的格式,或第一个run的格式) if placeholder_run_format: try: @@ -328,10 +366,24 @@ class DocumentService: run_element = runs_info[i]['run']._element try: paragraph._element.remove(run_element) - except: - pass + except Exception as remove_error: + print(f"[WARN] 删除run时出错: {str(remove_error)}") - print(f"[DEBUG] 段落替换了 {replacement_count} 个占位符(保持格式): '{full_text[:50]}...' -> '{final_text[:50]}...'") + # 最终验证:检查段落文本是否包含占位符 + final_paragraph_text = paragraph.text + remaining_in_paragraph = [] + for field_code in field_data.keys(): + placeholder = f"{{{{{field_code}}}}}" + if placeholder in final_paragraph_text: + remaining_in_paragraph.append(placeholder) + + if remaining_in_paragraph: + print(f"[WARN] 段落替换后仍有占位符: {remaining_in_paragraph}") + print(f"[WARN] 原始文本: '{full_text[:100]}'") + print(f"[WARN] 替换后文本: '{final_text[:100]}'") + print(f"[WARN] 段落实际文本: '{final_paragraph_text[:100]}'") + else: + print(f"[DEBUG] 段落替换了 {replacement_count} 个占位符(保持格式): '{full_text[:50]}...' -> '{final_text[:50]}...'") except Exception as e: # 如果单个段落处理失败,记录错误但继续处理其他段落 @@ -452,8 +504,135 @@ class DocumentService: # 保存到临时文件 temp_dir = tempfile.gettempdir() output_file = os.path.join(temp_dir, f"filled_{datetime.now().strftime('%Y%m%d%H%M%S')}.docx") - doc.save(output_file) - print(f"[DEBUG] 文档已保存到: {output_file}") + + # 保存文档前,再次验证替换结果(用于调试) + print(f"[DEBUG] 保存前验证:检查文档中是否还有占位符...") + verification_placeholders = set() + for paragraph in doc.paragraphs: + text = paragraph.text + matches = placeholder_pattern.findall(text) + for match in matches: + field_code = match.strip() + if field_code: + verification_placeholders.add(field_code) + + for table in doc.tables: + try: + if not table.rows: + continue + for row in table.rows: + try: + if not hasattr(row, 'cells'): + continue + try: + cells = row.cells + except (IndexError, AttributeError): + continue + for cell in cells: + try: + if hasattr(cell, 'paragraphs'): + for paragraph in cell.paragraphs: + text = paragraph.text + matches = placeholder_pattern.findall(text) + for match in matches: + field_code = match.strip() + if field_code: + verification_placeholders.add(field_code) + except Exception: + continue + except Exception: + continue + except Exception: + continue + + if verification_placeholders: + print(f"[WARN] 保存前验证发现仍有占位符: {sorted(verification_placeholders)}") + else: + print(f"[DEBUG] 保存前验证通过:所有占位符已替换") + + # 保存文档 + try: + doc.save(output_file) + print(f"[DEBUG] 文档已保存到: {output_file}") + + # 验证文件是否真的存在且大小大于0 + import time + time.sleep(0.1) # 等待文件系统同步(Ubuntu上可能需要) + + if not os.path.exists(output_file): + raise Exception(f"文件保存失败:文件不存在 {output_file}") + + file_size = os.path.getsize(output_file) + if file_size == 0: + raise Exception(f"文件保存失败:文件大小为0 {output_file}") + + print(f"[DEBUG] 文件保存验证通过:文件大小 {file_size} 字节") + + # 验证保存的文件内容是否正确(重新打开文件检查) + try: + verify_doc = Document(output_file) + verify_placeholders_in_saved = set() + for paragraph in verify_doc.paragraphs: + text = paragraph.text + matches = placeholder_pattern.findall(text) + for match in matches: + field_code = match.strip() + if field_code: + verify_placeholders_in_saved.add(field_code) + + for table in verify_doc.tables: + try: + if not table.rows: + continue + for row in table.rows: + try: + if not hasattr(row, 'cells'): + continue + try: + cells = row.cells + except (IndexError, AttributeError): + continue + for cell in cells: + try: + if hasattr(cell, 'paragraphs'): + for paragraph in cell.paragraphs: + text = paragraph.text + matches = placeholder_pattern.findall(text) + for match in matches: + field_code = match.strip() + if field_code: + verify_placeholders_in_saved.add(field_code) + except Exception: + continue + except Exception: + continue + except Exception: + continue + + if verify_placeholders_in_saved: + print(f"[WARN] 保存后验证:文件中仍有占位符: {sorted(verify_placeholders_in_saved)}") + print(f"[WARN] 这可能是导致Ubuntu上占位符未替换的原因") + else: + print(f"[DEBUG] 保存后验证通过:文件中所有占位符已替换") + except Exception as verify_error: + print(f"[WARN] 保存后验证失败(不影响功能): {str(verify_error)}") + + # 在Ubuntu上,可能需要显式同步文件系统 + try: + import sys + if sys.platform != 'win32': + # 在非Windows系统上,尝试同步文件系统 + os.sync() + print(f"[DEBUG] 已同步文件系统(非Windows系统)") + except Exception as sync_error: + print(f"[WARN] 文件系统同步失败(不影响功能): {str(sync_error)}") + + except Exception as save_error: + error_msg = f"保存文档失败: {str(save_error)}" + print(f"[ERROR] {error_msg}") + import traceback + print(traceback.format_exc()) + raise Exception(error_msg) return output_file diff --git a/template_finish/2-初核模版/2.谈话审批/走读式谈话审批/~$2谈话审批表.docx b/template_finish/2-初核模版/2.谈话审批/走读式谈话审批/~$2谈话审批表.docx deleted file mode 100644 index 8efa2ad..0000000 Binary files a/template_finish/2-初核模版/2.谈话审批/走读式谈话审批/~$2谈话审批表.docx and /dev/null differ diff --git a/修复Ubuntu占位符替换问题.md b/修复Ubuntu占位符替换问题.md new file mode 100644 index 0000000..d2f446d --- /dev/null +++ b/修复Ubuntu占位符替换问题.md @@ -0,0 +1,125 @@ +# 修复Ubuntu环境下占位符未替换问题 + +## 问题描述 + +在本地Windows环境下生成的谈话审批表内容正确,占位符被正确替换,但在远程Ubuntu服务器上生成的文档中占位符没有被正确替换。 + +## 可能的原因 + +1. **文件保存问题**:在Ubuntu上,文件保存后可能没有正确刷新到磁盘 +2. **编码问题**:Windows和Ubuntu在处理文件编码时可能有差异 +3. **文件系统同步问题**:Ubuntu上可能需要显式同步文件系统 +4. **占位符匹配问题**:可能因为编码或格式问题导致占位符没有被正确识别 + +## 修复内容 + +### 1. 增强占位符替换逻辑 + +- 添加了正则表达式匹配作为备用方案,确保能够识别各种格式的占位符 +- 增强了替换逻辑,使用多种方式检查占位符是否存在 + +### 2. 增强文件保存验证 + +- 保存后验证文件是否存在且大小大于0 +- 在非Windows系统上显式同步文件系统(使用`os.sync()`) +- 保存后重新打开文件验证内容是否正确 + +### 3. 增强调试信息 + +- 添加了详细的调试日志,记录每个替换步骤 +- 在替换前后验证占位符是否存在 +- 记录替换的详细信息,便于诊断问题 + +### 4. 增强替换后验证 + +- 替换后立即验证段落文本是否还包含占位符 +- 如果验证失败,记录详细的错误信息 +- 多次尝试写入,确保文本被正确写入 + +## 修改的文件 + +- `services/document_service.py` + +## 主要修改点 + +1. **`replace_placeholder_in_paragraph`函数**: + - 添加了正则表达式匹配作为备用方案 + - 增强了占位符检测逻辑 + - 添加了替换后的验证步骤 + +2. **文件保存部分**: + - 添加了文件保存后的验证 + - 在Ubuntu上显式同步文件系统 + - 保存后重新打开文件验证内容 + +3. **调试信息**: + - 添加了详细的调试日志 + - 记录每个替换步骤的详细信息 + +## 如何验证修复 + +1. **查看日志**: + - 在Ubuntu服务器上运行服务时,查看控制台输出的调试信息 + - 特别关注以下日志: + - `[DEBUG] 替换占位符: ...` + - `[DEBUG] 保存前验证:检查文档中是否还有占位符...` + - `[DEBUG] 保存后验证通过:文件中所有占位符已替换` + - `[WARN] 保存后验证:文件中仍有占位符: ...` + +2. **测试文档生成**: + - 在Ubuntu服务器上生成谈话审批表 + - 下载生成的文档,检查占位符是否被正确替换 + - 如果仍有问题,查看日志中的警告信息 + +3. **对比测试**: + - 在Windows和Ubuntu上使用相同的数据生成文档 + - 对比生成的文档内容 + - 查看日志中的差异 + +## 如果问题仍然存在 + +如果修复后问题仍然存在,请检查以下内容: + +1. **查看日志**: + - 检查是否有 `[WARN]` 或 `[ERROR]` 日志 + - 特别关注占位符替换相关的警告 + +2. **检查字段数据**: + - 确认传入的`field_data`包含所有需要的字段 + - 确认字段值不为空 + +3. **检查模板文件**: + - 确认模板文件中的占位符格式正确(`{{field_code}}`) + - 确认占位符没有被其他字符包围 + +4. **检查python-docx版本**: + - 确认Windows和Ubuntu上使用的是相同版本的`python-docx` + - 当前版本:`python-docx==1.1.0` + +5. **检查文件权限**: + - 确认Ubuntu服务器上的临时目录有写入权限 + - 确认MinIO上传功能正常 + +## 进一步诊断 + +如果问题仍然存在,可以: + +1. **启用更详细的日志**: + - 在代码中添加更多调试信息 + - 记录每个步骤的详细信息 + +2. **对比文件内容**: + - 下载Windows和Ubuntu上生成的文档 + - 使用工具对比文件内容,找出差异 + +3. **检查环境差异**: + - 对比Windows和Ubuntu上的Python版本 + - 对比依赖包的版本 + - 检查系统编码设置 + +## 注意事项 + +- 修复后的代码在保存文件时会等待0.1秒,确保文件系统同步 +- 在Ubuntu上会显式调用`os.sync()`同步文件系统 +- 如果文件保存验证失败,会抛出异常,阻止错误文件被上传 +