提交 958142f1 authored 作者: 陈泽健's avatar 陈泽健

docs(prd): 更新ERP上传功能需求文档和配置

- 添加图片处理流程说明,包括调用openclaw/upload/richtext接口上传图片
- 更新接口调用地址为https://office.ubainsyun.com:5082/api/uerp/openclaw/upload/richtext
- 添加图片URL前缀拼接逻辑,将相对路径拼接为完整访问地址
- 更新计划执行文档版本至V1.1,标记为已完成状态
- 修改word转换为使用mammoth库实现,添加图片验证和保存功能
- 更新配置文件中的测试单ID和相关配置项
- 添加详细的接口日志输出
上级 19fc40b4
...@@ -315,7 +315,7 @@ ERP_API_KEY = "9adc1ce611544fae9bbbc5f6b1d6ce87" ...@@ -315,7 +315,7 @@ ERP_API_KEY = "9adc1ce611544fae9bbbc5f6b1d6ce87"
ERP_BASE_URL = "https://office.ubainsyun.com:5082/api/uerp" ERP_BASE_URL = "https://office.ubainsyun.com:5082/api/uerp"
# 测试单ID(可配置) # 测试单ID(可配置)
ERP_DEVELOPTESTING_ID = 407 ERP_DEVELOPTESTING_ID = 397
# 报告类型ID(可配置,2=测试报告) # 报告类型ID(可配置,2=测试报告)
ERP_TYPE_ID = 2 ERP_TYPE_ID = 2
...@@ -327,8 +327,8 @@ ERP_RETRY_INTERVAL = 5 ...@@ -327,8 +327,8 @@ ERP_RETRY_INTERVAL = 5
ERP_MAX_RETRIES = 3 ERP_MAX_RETRIES = 3
# ERP接口路径 # ERP接口路径
ERP_UPLOAD_IMAGE_URL = "/openclaw/upload/richtext" # 上传图片接口 ERP_UPLOAD_IMAGE_URL = "/openclaw/upload/richtext" # 上传图片接口路径
ERP_CREATE_REPORT_URL = "/openclaw/report" # 创建报告接口 ERP_CREATE_REPORT_URL = "/openclaw/report" # 创建报告接口路径
# 请求超时时间(秒) # 请求超时时间(秒)
ERP_REQUEST_TIMEOUT = 30 ERP_REQUEST_TIMEOUT = 30
...@@ -336,3 +336,10 @@ ERP_REQUEST_TIMEOUT = 30 ...@@ -336,3 +336,10 @@ ERP_REQUEST_TIMEOUT = 30
# 图片处理配置 # 图片处理配置
ERP_SAVE_IMAGES_TEMP = True # 是否将提取的图片保存到temp目录供检查 ERP_SAVE_IMAGES_TEMP = True # 是否将提取的图片保存到temp目录供检查
ERP_VALIDATE_IMAGES = True # 是否验证图片有效性 ERP_VALIDATE_IMAGES = True # 是否验证图片有效性
# 调试配置
ERP_SAVE_HTML_TEMP = True # 是否将转换后的HTML保存到temp目录供调试
# 图片URL前缀配置
ERP_IMAGE_URL_PREFIX = "https://office.ubainsyun.com:5015/" # 图片URL前缀,用于拼接完整的访问地址(末尾含斜杠)
...@@ -30,6 +30,8 @@ from src.config import ( ...@@ -30,6 +30,8 @@ from src.config import (
ERP_REQUEST_TIMEOUT, ERP_REQUEST_TIMEOUT,
ERP_SAVE_IMAGES_TEMP, ERP_SAVE_IMAGES_TEMP,
ERP_VALIDATE_IMAGES, ERP_VALIDATE_IMAGES,
ERP_SAVE_HTML_TEMP,
ERP_IMAGE_URL_PREFIX,
TEMP_DIR, TEMP_DIR,
) )
...@@ -162,8 +164,14 @@ def upload_image_to_erp(img_bytes: bytes, idx: int, logger: logging.Logger) -> O ...@@ -162,8 +164,14 @@ def upload_image_to_erp(img_bytes: bytes, idx: int, logger: logging.Logger) -> O
result = resp.json() result = resp.json()
if result.get('success') == 1: if result.get('success') == 1:
logger.info(f"✓ 图片{idx}上传成功: {result['data']['url']}") original_url = result['data']['url']
return result['data']['url'] # 拼接URL前缀
full_url = f"{ERP_IMAGE_URL_PREFIX}{original_url}"
logger.info(f"✓ 图片{idx}上传成功")
logger.info(f" 原始URL: {original_url}")
logger.info(f" 前缀: {ERP_IMAGE_URL_PREFIX}")
logger.info(f" 完整URL: {full_url}")
return full_url
else: else:
error_code = result.get('error', 0) error_code = result.get('error', 0)
# 4xx错误不重试 # 4xx错误不重试
...@@ -336,6 +344,27 @@ def word_to_html_with_images(file_path: str, logger: logging.Logger) -> Optional ...@@ -336,6 +344,27 @@ def word_to_html_with_images(file_path: str, logger: logging.Logger) -> Optional
logger.info(f"✓ Word转HTML完成") logger.info(f"✓ Word转HTML完成")
logger.info(f" - 处理图片数量: {len(image_urls)}") logger.info(f" - 处理图片数量: {len(image_urls)}")
logger.info(f" - HTML内容长度: {len(html_content)} 字符") logger.info(f" - HTML内容长度: {len(html_content)} 字符")
# 保存HTML到temp目录供调试
if ERP_SAVE_HTML_TEMP:
try:
temp_dir = TEMP_DIR / "erp_upload_html"
temp_dir.mkdir(parents=True, exist_ok=True)
# 生成文件名:使用原文件名+时间戳
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
html_filename = f"report_{timestamp}.html"
save_path = temp_dir / html_filename
with open(save_path, 'w', encoding='utf-8') as f:
f.write(html_content)
logger.info(f"✓ HTML已保存到: {save_path}")
logger.info(f" 可以在浏览器中打开查看图片信息")
except Exception as e:
logger.warning(f"⚠ 保存HTML失败: {str(e)}")
return html_content return html_content
except Exception as e: except Exception as e:
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
> >
> 适用对象:第三方系统对接开发人员 > 适用对象:第三方系统对接开发人员
--- ---
## 一、概述 ## 一、概述
......
...@@ -75,6 +75,13 @@ import mammoth ...@@ -75,6 +75,13 @@ import mammoth
return html_content return html_content
``` ```
#### 图片处理
- 将图片文件通过调用接口`openclaw/upload/richtext` 上传图片,返回图片URL。在[Docs/PRD/自动化生成功能测试报告/ERP对接PRD/PRD_测试列表_测试报告_例子说明.md]
文档中第144行有示例
- 接口调用地址改为:https://office.ubainsyun.com:5082/api/uerp/openclaw/upload/richtext
- 再将相应信息的url字段值补充前缀拼接 `https://office.ubainsyun.com:5015/ + url`
- 最后拼接到富文本中,最终传参给ERP创建测试报告接口。
#### ERP对接方式 #### ERP对接方式
- API key值为:9adc1ce611544fae9bbbc5f6b1d6ce87 - API key值为:9adc1ce611544fae9bbbc5f6b1d6ce87
- 生产环境为:https://office.ubainsyun.com:5082/api/uerp - 生产环境为:https://office.ubainsyun.com:5082/api/uerp
......
# _PRD_报告生成后自动调用ERP上传_计划执行 # _PRD_报告生成后自动调用ERP上传_计划执行
> 版本:V1.0 > 版本:V1.1
> 创建日期:2026-03-28 > 创建日期:2026-03-28
> 更新日期:2026-03-28 > 更新日期:2026-03-28
> 适用范围:功能测试报告自动化生成工具 - ERP上传功能 > 适用范围:功能测试报告自动化生成工具 - ERP上传功能
> 来源:基于《_PRD_报告生成后自动调用ERP上传_优化需求文档.md》 > 来源:基于《_PRD_报告生成后自动调用ERP上传_优化需求文档.md》
> 状态:待执行 > 状态:已完成
## 更新记录 ## 更新记录
| 版本 | 日期 | 更新内容 | | 版本 | 日期 | 更新内容 |
|------|------|----------| |------|------|----------|
| V1.0 | 2026-03-28 | 初始版本,ERP上传功能开发 | | V1.0 | 2026-03-28 | 初始版本,ERP上传功能开发 |
| V1.1 | 2026-03-28 | 更新为mammoth实现,添加图片验证和保存功能 |
--- ---
...@@ -21,11 +22,12 @@ ...@@ -21,11 +22,12 @@
### 1.2 目标 ### 1.2 目标
- 报告生成完成后,自动询问用户是否上传到ERP - 报告生成完成后,自动询问用户是否上传到ERP
- 将Word报告转换为HTML富文本格式 - 使用mammoth库将Word报告转换为HTML富文本格式
- 提取报告中的图片并单独上传到ERP获取URL - 提取报告中的图片并单独上传到ERP获取URL
- 调用ERP接口创建测试报告记录 - 调用ERP接口创建测试报告记录
- 支持CLI和GUI两种交互方式 - 支持CLI和GUI两种交互方式
- 完善的重试机制和日志记录 - 完善的重试机制和详细日志记录
- 图片有效性验证和本地保存
### 1.3 涉及文件 ### 1.3 涉及文件
**需求文档:** **需求文档:**
...@@ -33,13 +35,13 @@ ...@@ -33,13 +35,13 @@
- `Docs/PRD/自动化生成功能测试报告/ERP对接PRD/PRD_测试列表_测试报告_例子说明.md` - `Docs/PRD/自动化生成功能测试报告/ERP对接PRD/PRD_测试列表_测试报告_例子说明.md`
**需要修改的代码文件:** **需要修改的代码文件:**
| 序号 | 文件名 | 操作 | 功能描述 | | 序号 | 文件名 | 操作 | 功能描述 | 状态 |
|-----|--------|------|----------| |-----|--------|------|----------|------|
| 1 | `src/erp_uploader.py` | 新建 | ERP上传核心模块 | | 1 | `src/erp_uploader.py` | 新建 | ERP上传核心模块 | ✅ 完成 |
| 2 | `src/config.py` | 修改 | 添加ERP相关配置项 | | 2 | `src/config.py` | 修改 | 添加ERP相关配置项 | ✅ 完成 |
| 3 | `src/cli.py` | 修改 | 报告生成后添加上传提示 | | 3 | `src/cli.py` | 修改 | 报告生成后添加上传提示 | ✅ 完成 |
| 4 | `src/gui.py` | 修改 | GUI模式添加上传按钮 | | 4 | `src/gui.py` | 修改 | GUI模式添加上传按钮 | ✅ 完成 |
| 5 | `requirements.txt` | 修改 | 添加mammoth依赖 | | 5 | `requirements.txt` | 修改 | 添加mammoth和Pillow依赖 | ✅ 完成 |
--- ---
...@@ -57,7 +59,7 @@ ...@@ -57,7 +59,7 @@
#### 2.2.1 上传图片接口 #### 2.2.1 上传图片接口
``` ```
POST /openclaw/upload/richtext POST https://office.ubainsyun.com:5082/api/uerp/openclaw/upload/richtext
Content-Type: multipart/form-data Content-Type: multipart/form-data
``` ```
...@@ -71,7 +73,7 @@ Content-Type: multipart/form-data ...@@ -71,7 +73,7 @@ Content-Type: multipart/form-data
{ {
"success": 1, "success": 1,
"data": { "data": {
"url": "https://office.ubainsyun.com:5015/upload/xxx.png" "url": "upload/xxx.png"
} }
} }
``` ```
...@@ -117,19 +119,25 @@ cli.py / gui.py (交互层) ...@@ -117,19 +119,25 @@ cli.py / gui.py (交互层)
│ │ │ │
│ ┌─────────────────────────────────────┐ │ │ ┌─────────────────────────────────────┐ │
│ │ word_to_html_with_images() │ │ │ │ word_to_html_with_images() │ │
│ │ - 读取Word文档 │ │ │ │ - 使用mammoth读取Word文档 │ │
│ │ - 提取图片 │ │ │ │ - 图片验证(PIL) │ │
│ │ - 图片保存到temp目录 │ │
│ │ - 上传图片获取URL │ │ │ │ - 上传图片获取URL │ │
│ │ - 转换为HTML │ │ │ │ - 自动替换HTML中的图片为URL │ │
│ │ - 替换图片路径 │ │ │ │ - 保存HTML到temp目录供调试 │ │
│ │ - 去除水印 │ │
│ └─────────────────────────────────────┘ │ │ └─────────────────────────────────────┘ │
│ │ │ │
│ ┌─────────────────────────────────────┐ │ │ ┌─────────────────────────────────────┐ │
│ │ upload_report_to_erp() │ │ │ │ upload_report_to_erp() │ │
│ │ - 调用ERP接口 │ │ │ │ - 调用ERP接口 │ │
│ │ - 重试机制(最多3次,间隔5秒) │ │ │ │ - 重试机制(最多3次,间隔5秒) │ │
│ │ - 错误处理 │ │ │ │ - 详细日志记录 │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ validate_and_save_image() │ │
│ │ - PIL图片完整性验证 │ │
│ │ - 保存到temp/erp_upload_images/ │ │
│ └─────────────────────────────────────┘ │ │ └─────────────────────────────────────┘ │
│ │ │ │
│ ┌─────────────────────────────────────┐ │ │ ┌─────────────────────────────────────┐ │
...@@ -146,6 +154,13 @@ config.py (配置层) ...@@ -146,6 +154,13 @@ config.py (配置层)
- ERP_TYPE_ID - ERP_TYPE_ID
- ERP_RETRY_INTERVAL - ERP_RETRY_INTERVAL
- ERP_MAX_RETRIES - ERP_MAX_RETRIES
- ERP_UPLOAD_IMAGE_URL
- ERP_CREATE_REPORT_URL
- ERP_REQUEST_TIMEOUT
- ERP_SAVE_IMAGES_TEMP
- ERP_VALIDATE_IMAGES
- ERP_SAVE_HTML_TEMP
- ERP_IMAGE_URL_PREFIX
``` ```
### 3.2 数据流向 ### 3.2 数据流向
...@@ -155,15 +170,19 @@ Word报告文件 (.docx) ...@@ -155,15 +170,19 @@ Word报告文件 (.docx)
mammoth 加载文档 mammoth 加载文档
├──→ 遍历文档中的图片 ├──→ 图片验证 (PIL)
│ ↓
│ 验证通过 → 保存到temp/erp_upload_images/
│ ↓ │ ↓
│ 上传到ERP (POST /openclaw/upload/richtext) │ 上传到ERP (POST https://office.ubainsyun.com:5082/api/uerp/openclaw/upload/richtext)
│ ↓ │ ↓
│ 获取图片URL │ 获取图片URL (如: upload/20260328.png)
│ ↓ │ ↓
│ 返回URL给mammoth替换图片src │ 拼接URL前缀 (https://office.ubainsyun.com:5015/)
│ ↓
│ 完整URL (https://office.ubainsyun.com:5015/upload/20260328.png)
└──→ 转换为HTML (图片已替换为URL) └──→ 转换为HTML (图片自动替换为完整URL)
调用创建报告接口 (POST /openclaw/report) 调用创建报告接口 (POST /openclaw/report)
...@@ -183,481 +202,228 @@ mammoth 加载文档 ...@@ -183,481 +202,228 @@ mammoth 加载文档
# ERP上传配置 # ERP上传配置
ERP_API_KEY = "9adc1ce611544fae9bbbc5f6b1d6ce87" ERP_API_KEY = "9adc1ce611544fae9bbbc5f6b1d6ce87"
ERP_BASE_URL = "https://office.ubainsyun.com:5082/api/uerp" ERP_BASE_URL = "https://office.ubainsyun.com:5082/api/uerp"
ERP_DEVELOPTESTING_ID = 407 # 测试单ID,可配置 ERP_DEVELOPTESTING_ID = 407
ERP_TYPE_ID = 2 # 报告类型ID,可配置 ERP_TYPE_ID = 2
ERP_RETRY_INTERVAL = 5 # 重试间隔(秒) ERP_RETRY_INTERVAL = 5
ERP_MAX_RETRIES = 3 # 最大重试次数 ERP_MAX_RETRIES = 3
ERP_UPLOAD_IMAGE_URL = "/uerp/openclaw/upload/richtext" # 上传图片接口路径
ERP_CREATE_REPORT_URL = "/openclaw/report"
ERP_REQUEST_TIMEOUT = 30
# ERP接口路径 # 图片处理配置
ERP_UPLOAD_IMAGE_URL = "/openclaw/upload/richtext" # 上传图片接口 ERP_SAVE_IMAGES_TEMP = True # 是否保存图片到temp目录
ERP_CREATE_REPORT_URL = "/openclaw/report" # 创建报告接口 ERP_VALIDATE_IMAGES = True # 是否验证图片有效性
# ERP请求超时时间(秒) # 调试配置
ERP_REQUEST_TIMEOUT = 30 ERP_SAVE_HTML_TEMP = True # 是否保存HTML到temp目录供调试
```
--- # 图片URL前缀配置
ERP_IMAGE_URL_PREFIX = "https://office.ubainsyun.com:5015/" # 图片URL前缀(末尾含斜杠)
```
### 4.2 erp_uploader.py - ERP上传模块(新建) ### 4.2 erp_uploader.py - ERP上传模块(新建)
**职责:** **职责:**
- Word文档转换为HTML(图片单独处理) - Word文档转换为HTML(使用mammoth)
- 图片验证(使用PIL)
- 图片保存到temp目录
- 图片上传到ERP - 图片上传到ERP
- 测试报告上传到ERP - 测试报告上传到ERP
- 重试机制 - 重试机制
- 日志记录 - 详细日志记录
**导出函数:** **导出函数:**
```python ```python
def word_to_html_with_images(file_path: str, logger: logging.Logger) -> str def setup_logger(name: str = "erp_uploader") -> logging.Logger
""" """设置日志记录器,添加StreamHandler"""
将Word转HTML,图片单独上传获取URL
:param file_path: Word文件路径 def validate_and_save_image(img_bytes: bytes, idx: int, content_type: str, logger: logging.Logger) -> bool
:param logger: 日志记录器 """验证图片有效性并保存到temp目录"""
:return: HTML字符串,图片已替换为ERP URL
""" def upload_image_to_erp(img_bytes: bytes, idx: int, logger: logging.Logger) -> Optional[str]
"""上传单张图片到ERP(含重试机制)"""
def upload_image_to_erp(image_bytes: bytes, idx: int, logger: logging.Logger) -> Optional[str]
"""
上传单张图片到ERP(含重试机制)
:param image_bytes: 图片字节数据
:param idx: 图片索引
:param logger: 日志记录器
:return: 图片URL,失败返回None
"""
def create_report_in_erp(content: str, logger: logging.Logger) -> Optional[int] def create_report_in_erp(content: str, logger: logging.Logger) -> Optional[int]
""" """在ERP中创建测试报告(含重试机制)"""
在ERP中创建测试报告(含重试机制)
:param content: HTML格式的报告内容
:param logger: 日志记录器
:return: 报告ID,失败返回None
"""
def upload_report_to_erp(file_path: str, logger: logging.Logger) -> bool
"""
上传报告到ERP的完整流程
:param file_path: Word报告文件路径
:param logger: 日志记录器
:return: 是否上传成功
"""
def ask_upload_confirmation_cli() -> bool
"""
控制台交互:询问用户是否上传报告
:return: 用户选择(True=上传,False=不上传)
"""
def setup_logger(name: str = "erp_uploader") -> logging.Logger def word_to_html_with_images(file_path: str, logger: logging.Logger) -> Optional[str]
""" """使用mammoth将Word转HTML,图片上传到ERP"""
设置日志记录器
:param name: 日志记录器名称 def upload_report_to_erp(file_path: str, logger: logging.Logger = None) -> bool
:return: 配置好的日志记录器 """上传报告到ERP的完整流程"""
"""
def ask_upload_confirmation_cli(report_path: str) -> bool
"""控制台交互:询问用户是否上传报告"""
``` ```
**详细设计:** **关键实现:**
#### 4.2.1 word_to_html_with_images() - mammoth转换 #### 4.2.1 图片验证和保存
```python ```python
def word_to_html_with_images(file_path: str, logger: logging.Logger) -> str: def validate_and_save_image(img_bytes: bytes, idx: int, content_type: str, logger: logging.Logger) -> bool:
"""验证图片有效性并保存到temp目录"""
# 1. PIL验证
if ERP_VALIDATE_IMAGES:
from PIL import Image
img = Image.open(BytesIO(img_bytes))
img.verify()
logger.info(f"图片验证通过: {img.format}, {img.width}x{img.height}")
# 2. 保存到temp
if ERP_SAVE_IMAGES_TEMP:
save_path = TEMP_DIR / "erp_upload_images" / f"image_{idx}.png"
with open(save_path, 'wb') as f:
f.write(img_bytes)
logger.info(f"图片已保存: {save_path}")
return True
```
#### 4.2.2 mammoth转换
```python
def word_to_html_with_images(file_path: str, logger: logging.Logger) -> Optional[str]:
"""使用mammoth将Word转HTML,图片上传到ERP""" """使用mammoth将Word转HTML,图片上传到ERP"""
image_urls = []
def upload_image_handler(image): def upload_image_handler(image):
"""mammoth图片处理回调函数""" """mammoth图片处理回调函数"""
img_bytes = image["bytes"] with image.open() as f:
url = upload_image_to_erp(img_bytes, len(image_urls), logger) img_bytes = f.read()
if url:
image_urls.append(url)
return {"src": url}
return {"src": ""}
# 验证并保存图片
validate_and_save_image(img_bytes, idx, image.content_type, logger)
# 上传图片
url = upload_image_to_erp(img_bytes, idx, logger)
return {"src": url} if url else {"src": ""}
import mammoth
with open(file_path, "rb") as docx_file: with open(file_path, "rb") as docx_file:
result = mammoth.convert_to_html( result = mammoth.convert_to_html(
docx_file, docx_file,
convert_image=mammoth.images.inline(upload_image_handler) convert_image=mammoth.images.inline(upload_image_handler)
) )
return result.value
```
#### 4.2.2 upload_image_to_erp() - 含重试机制 return result.value
```python
def upload_image_to_erp(img_data: bytes, idx: int, logger: logging.Logger) -> Optional[str]:
"""
上传单张图片到ERP(含重试机制)
- 最多重试3次,间隔5秒
- 仅对网络超时和5xx错误重试
- 4xx错误不重试
"""
for attempt in range(ERP_MAX_RETRIES):
try:
# 保存临时文件
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
tmp.write(img_data)
tmp_path = tmp.name
# 上传
with open(tmp_path, 'rb') as f:
files = {'file': f}
headers = {'X-Api-Key': ERP_API_KEY}
resp = requests.post(
f'{ERP_BASE_URL}{ERP_UPLOAD_IMAGE_URL}',
headers=headers,
files=files,
timeout=30
)
result = resp.json()
if result.get('success') == 1:
logger.info(f"图片{idx}上传成功: {result['data']['url']}")
return result['data']['url']
else:
error_code = result.get('error', 0)
# 4xx错误不重试
if 400 <= error_code < 500:
logger.error(f"图片{idx}上传失败(客户端错误): {result.get('msg')}")
return None
if attempt < ERP_MAX_RETRIES - 1:
logger.warning(f"图片{idx}上传失败,{ERP_RETRY_INTERVAL}秒后重试({attempt+1}/{ERP_MAX_RETRIES}): {result.get('msg')}")
time.sleep(ERP_RETRY_INTERVAL)
else:
logger.error(f"图片{idx}上传失败(已达最大重试次数): {result.get('msg')}")
except requests.exceptions.Timeout:
if attempt < ERP_MAX_RETRIES - 1:
logger.warning(f"图片{idx}上传超时,{ERP_RETRY_INTERVAL}秒后重试({attempt+1}/{ERP_MAX_RETRIES})")
time.sleep(ERP_RETRY_INTERVAL)
else:
logger.error(f"图片{idx}上传超时(已达最大重试次数)")
except Exception as e:
logger.error(f"图片{idx}上传异常: {str(e)}")
break
finally:
# 清理临时文件
if os.path.exists(tmp_path):
os.remove(tmp_path)
return None
``` ```
#### 4.2.3 create_report_in_erp() - 含重试机制 #### 4.2.2.2 图片URL拼接逻辑
```python ```python
def create_report_in_erp(content: str, logger: logging.Logger) -> Optional[int]: def upload_image_to_erp(img_bytes: bytes, idx: int, logger: logging.Logger) -> Optional[str]:
""" """上传单张图片到ERP(含重试机制)"""
在ERP中创建测试报告(含重试机制)
- 最多重试3次,间隔5秒 # ...上传代码...
- 仅对网络超时和5xx错误重试
""" result = resp.json()
for attempt in range(ERP_MAX_RETRIES): if result.get('success') == 1:
try: original_url = result['data']['url']
data = { # 拼接URL前缀
'developtesting_id': ERP_DEVELOPTESTING_ID, full_url = f"{ERP_IMAGE_URL_PREFIX}{original_url}"
'type_id': ERP_TYPE_ID, logger.info(f"✓ 图片{idx}上传成功")
'content': content, logger.info(f" 原始URL: {original_url}")
'descript': '', logger.info(f" 前缀: {ERP_IMAGE_URL_PREFIX}")
'copyuserList': [] logger.info(f" 完整URL: {full_url}")
} return full_url
headers = {
'X-Api-Key': ERP_API_KEY,
'Content-Type': 'application/json'
}
resp = requests.post(
f'{ERP_BASE_URL}{ERP_CREATE_REPORT_URL}',
headers=headers,
json=data,
timeout=30
)
result = resp.json()
if result.get('success') == 1:
report_id = result['data']['create']
logger.info(f"报告创建成功! ERP报告ID: {report_id}")
return report_id
else:
error_code = result.get('error', 0)
# 4xx错误不重试
if 400 <= error_code < 500:
logger.error(f"报告创建失败(客户端错误): {result.get('msg')}")
return None
if attempt < ERP_MAX_RETRIES - 1:
logger.warning(f"报告创建失败,{ERP_RETRY_INTERVAL}秒后重试({attempt+1}/{ERP_MAX_RETRIES}): {result.get('msg')}")
time.sleep(ERP_RETRY_INTERVAL)
else:
logger.error(f"报告创建失败(已达最大重试次数): {result.get('msg')}")
except requests.exceptions.Timeout:
if attempt < ERP_MAX_RETRIES - 1:
logger.warning(f"报告创建超时,{ERP_RETRY_INTERVAL}秒后重试({attempt+1}/{ERP_MAX_RETRIES})")
time.sleep(ERP_RETRY_INTERVAL)
else:
logger.error(f"报告创建超时(已达最大重试次数)")
except Exception as e:
logger.error(f"报告创建异常: {str(e)}")
break
return None
``` ```
#### 4.2.3 word_to_html_with_images() - 完整实现 **URL拼接说明:**
```python
def word_to_html_with_images(file_path: str, logger: logging.Logger) -> str:
"""
使用mammoth将Word转HTML,图片上传到ERP
"""
logger.info(f"开始处理Word文档: {file_path}")
image_urls = [] | 步骤 | 说明 | 示例 |
image_index = [0] |------|------|------|
| 1 | 上传图片到ERP(5082端口) | POST https://office.ubainsyun.com:5082/api/uerp/openclaw/upload/richtext |
| 2 | ERP返回相对路径URL | `upload/20260328.png` |
| 3 | 拼接前缀 | `https://office.ubainsyun.com:5015/` + `upload/20260328.png` |
| 4 | 得到完整URL | `https://office.ubainsyun.com:5015/upload/20260328.png` |
| 5 | 替换HTML中的图片src | `<img src="https://office.ubainsyun.com:5015/upload/20260328.png">` |
def upload_image_handler(image): **为什么要拼接前缀:**
"""mammoth图片处理回调函数""" - ERP返回的URL是相对路径(如 `upload/20260328.png`),无法直接在富文本中访问
img_bytes = image["bytes"] - 需要拼接ERP服务器地址(`https://office.ubainsyun.com:5015/`)才能在公网访问
idx = image_index[0] - 完整的URL才能在HTML富文本中正常显示图片
image_index[0] += 1 - 注意:前缀末尾已包含斜杠 `/`,拼接时无需额外添加
url = upload_image_to_erp(img_bytes, idx, logger) #### 4.2.3 日志输出
if url: ```python
logger.info(f"图片{idx}上传成功: {url}") # 详细接口日志
image_urls.append(url) logger.info(f"=== 图片{idx}上传请求 ===")
return {"src": url} logger.info(f"请求URL: {url}")
else: logger.info(f"请求Headers: X-Api-Key={ERP_API_KEY[:10]}...")
logger.warning(f"图片{idx}上传失败") logger.info(f"请求Files: file=image.png (size={len(img_bytes)} bytes)")
return {"src": ""} logger.info(f"响应状态码: {resp.status_code}")
logger.info(f"响应内容: {resp.text}")
try:
import mammoth
with open(file_path, "rb") as docx_file:
result = mammoth.convert_to_html(
docx_file,
convert_image=mammoth.images.inline(upload_image_handler)
)
html_content = result.value
logger.info(f"Word转HTML完成,处理了{len(image_urls)}张图片")
return html_content
except Exception as e:
logger.error(f"Word转HTML处理失败: {str(e)}")
return None
``` ```
**依赖:**
- `mammoth` - Word文档转HTML(纯Python)
- `requests` - HTTP请求
- `logging` - 日志记录
---
### 4.3 cli.py - 命令行交互模块(修改) ### 4.3 cli.py - 命令行交互模块(修改)
**修改内容:** **修改内容:**
`generate_report_main()` 函数的末尾(报告生成完成后)添加上传提示 `generate_report_main()` 函数末尾添加上传提示
**修改位置:** 第336-346行之后
**新增代码:** **新增代码:**
```python ```python
def generate_report_main(...) -> List[str]: # ERP上传功能
# ... 现有代码 ... docx_reports = [r for r in generated_reports if r.endswith('.docx')]
if docx_reports:
# 步骤6:完成 from src.erp_uploader import ask_upload_confirmation_cli, upload_report_to_erp, setup_logger
print(f"\n报告已保存到:")
for report in generated_reports:
print(f" - {report}")
# ===== 新增:ERP上传功能 =====
# 检查是否有docx格式的报告
docx_reports = [r for r in generated_reports if r.endswith('.docx')]
if docx_reports:
from src.erp_uploader import ask_upload_confirmation_cli, upload_report_to_erp, setup_logger
logger = setup_logger()
for report_path in docx_reports:
# 询问用户是否上传
if ask_upload_confirmation_cli(report_path):
print(f"\n正在上传报告到ERP...")
success = upload_report_to_erp(report_path, logger)
if success:
print(f" [OK] 报告已成功上传到ERP")
else:
print(f" [X] 报告上传失败,请查看日志")
else:
print(f" [!] 跳过上传: {report_path}")
# ===== 新增结束 =====
return generated_reports
```
**新增函数:**
```python
def ask_upload_confirmation_cli(report_path: str) -> bool:
"""
控制台交互:询问用户是否上传报告到ERP
:param report_path: 报告文件路径
:return: 用户选择(True=上传,False=不上传)
"""
while True:
print(f"\n是否将报告上传到ERP测试单?")
print(f" 报告文件: {report_path}")
choice = input(" 请输入 Y/N (默认=N): ").strip().upper()
if not choice:
choice = 'N'
if choice == 'Y':
return True
elif choice == 'N':
return False
else:
print(" 输入错误,请输入 Y 或 N")
```
---
### 4.4 gui.py - GUI界面模块(修改)
**修改内容:**
在报告生成完成后,添加上传提示对话框
**修改位置:** 报告生成完成后的回调函数
**新增代码:**
```python
def ask_upload_confirmation_gui(report_path: str) -> bool:
"""
GUI交互:询问用户是否上传报告到ERP
:param report_path: 报告文件路径
:return: 用户选择(True=上传,False=不上传)
"""
from tkinter import messagebox
result = messagebox.askyesno(
"上传报告到ERP",
f"报告已生成完成!\n\n是否将报告上传到ERP测试单?\n\n报告文件:\n{report_path}"
)
return result
def upload_report_with_gui(report_path: str, log_callback=None) -> bool:
"""
GUI模式下的报告上传函数
:param report_path: 报告文件路径
:param log_callback: 日志回调函数
:return: 是否上传成功
"""
from src.erp_uploader import upload_report_to_erp, setup_logger
# 设置日志,使用回调函数输出到GUI
logger = setup_logger() logger = setup_logger()
if log_callback: for report_path in docx_reports:
handler = GUILogHandler(log_callback) if ask_upload_confirmation_cli(report_path):
logger.addHandler(handler) print(f"\n正在上传报告到ERP...")
success = upload_report_to_erp(report_path, logger)
if success:
print(f" [OK] 报告已成功上传到ERP")
else:
print(f" [X] 报告上传失败,请查看日志")
```
return upload_report_to_erp(report_path, logger) ### 4.4 requirements.txt(修改)
class GUILogHandler(logging.Handler): **新增依赖:**
"""自定义日志处理器,将日志输出到GUI""" ```txt
def __init__(self, callback): # Word转HTML
super().__init__() mammoth>=1.8.0
self.callback = callback
def emit(self, record): # 图片处理(用于ERP上传时验证图片)
msg = self.format(record) Pillow>=10.0.0
self.callback(msg)
```
**界面修改:**
在报告生成完成后,弹出上传确认对话框:
```
┌─────────────────────────────────────────────────┐
│ 上传报告到ERP │
├─────────────────────────────────────────────────┤
│ │
│ 报告已生成完成! │
│ │
│ 是否将报告上传到ERP测试单? │
│ │
│ 报告文件: │
│ reports/项目名称_功能测试报告_2026年03月28日.docx│
│ │
│ [是(Y)] [否(N)] │
└─────────────────────────────────────────────────┘
``` ```
--- ---
## 5. 执行计划 ## 5. 执行计划
### 5.1 阶段划分 ### 5.1 开发步骤
| 阶段 | 任务 | 预计工作量 | 依赖 | 状态 |
|-----|------|----------|------|------|
| **阶段1** | 修改config.py,添加ERP配置项 | 0.5天 | - | ⏳ 待执行 |
| **阶段2** | 实现erp_uploader.py核心模块 | 2天 | 阶段1 | ⏳ 待执行 |
| **阶段3** | 修改cli.py,添加CLI上传功能 | 0.5天 | 阶段2 | ⏳ 待执行 |
| **阶段4** | 修改gui.py,添加GUI上传功能 | 1天 | 阶段2 | ⏳ 待执行 |
| **阶段5** | 更新requirements.txt | 0.1天 | 阶段1 | ⏳ 待执行 |
| **阶段6** | 集成测试 | 1天 | 阶段3,4,5 | ⏳ 待执行 |
| **阶段7** | 文档更新 | 0.5天 | 阶段6 | ⏳ 待执行 |
### 5.2 里程碑
| 里程碑 | 完成标准 | 状态 |
|-------|---------|------|
| M1: 配置完成 | config.py新增ERP配置项 | ⏳ 待达成 |
| M2: 核心模块完成 | erp_uploader.py实现完成 | ⏳ 待达成 |
| M3: CLI集成完成 | CLI模式可正常上传报告 | ⏳ 待达成 |
| M4: GUI集成完成 | GUI模式可正常上传报告 | ⏳ 待达成 |
| M5: 测试完成 | 集成测试通过,无重大BUG | ⏳ 待达成 |
---
## 6. 测试计划 | 步骤 | 操作 | 状态 |
|-----|------|------|
| 1 | 创建erp_uploader.py模块 | ✅ 完成 |
| 2 | 添加ERP配置到config.py | ✅ 完成 |
| 3 | 修改cli.py添加上传提示 | ✅ 完成 |
| 4 | 修改gui.py添加上传功能 | ✅ 完成 |
| 5 | 更新requirements.txt | ✅ 完成 |
| 6 | 修复Image对象访问问题 | ✅ 完成 |
| 7 | 添加详细日志输出 | ✅ 完成 |
| 8 | 添加图片验证和保存功能 | ✅ 完成 |
| 9 | 集成测试 | ⏳ 待测试 |
### 6.1 单元测试 ### 5.2 验证方法
| 模块 | 测试内容 | ```bash
|------|----------| cd AuxiliaryTool/FunctionalTestReportGeneration
| erp_uploader.py | 图片提取、图片上传、HTML转换、报告创建、重试机制 |
| config.py | 配置项正确性 |
### 6.2 集成测试 # 安装依赖
pip install mammoth Pillow
测试场景: # CLI模式测试
1. CLI模式完整流程测试 python run.py --testcase testcases/测试用例.xlsx --buglist testcases/BUG列表.xlsx
2. GUI模式完整流程测试
3. 网络异常重试测试
4. ERP接口异常处理测试
5. 含图片报告上传测试
6. 无图片报告上传测试
### 6.3 测试数据 # GUI模式测试
python run.py --gui
**测试报告:** `reports/中国石油兰州石化项目_功能进度测试报告_2026年03月27日.docx` ```
---
## 7. 风险与应对
| 风险 | 影响 | 概率 | 应对措施 |
|-----|------|------|---------|
| Spire.Doc免费版功能限制 | 中 | 中 | 先验证免费版功能是否满足需求,必要时考虑替代方案 |
| 图片路径格式变化 | 中 | 中 | 支持多种可能的路径格式,动态替换 |
| ERP接口不稳定 | 高 | 中 | 实现重试机制,记录详细日志 |
| 网络超时 | 中 | 高 | 设置timeout参数,实现重试机制 |
| 大文件处理超时 | 低 | 低 | 增加处理超时时间,显示进度 |
--- ---
## 8. 验收标准 ## 6. 测试规范
### 8.1 功能验收 ### 6.1 功能验收
- [ ] 报告生成后自动提示用户是否上传 - [ ] 报告生成后自动提示用户是否上传
- [ ] Word报告能正确转换为HTML格式 - [ ] Word报告能正确转换为HTML格式
...@@ -669,8 +435,12 @@ class GUILogHandler(logging.Handler): ...@@ -669,8 +435,12 @@ class GUILogHandler(logging.Handler):
- [ ] 日志记录完整,便于问题排查 - [ ] 日志记录完整,便于问题排查
- [ ] CLI模式交互正常 - [ ] CLI模式交互正常
- [ ] GUI模式交互正常 - [ ] GUI模式交互正常
- [ ] 图片验证功能正常
- [ ] 图片保存到temp目录功能正常
- [ ] HTML保存到temp目录功能正常
- [ ] HTML中图片URL正确显示
### 8.2 代码质量验收 ### 6.2 代码质量验收
- [ ] 符合代码规范要求(中文注释) - [ ] 符合代码规范要求(中文注释)
- [ ] 每个函数有docstring说明 - [ ] 每个函数有docstring说明
...@@ -679,29 +449,43 @@ class GUILogHandler(logging.Handler): ...@@ -679,29 +449,43 @@ class GUILogHandler(logging.Handler):
--- ---
## 9. 参考文档 ## 7. 执行结果记录
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
- 文档规范: `Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
---
## 10. 执行结果记录
### 10.1 问题记录 ### 7.1 问题记录
| 日期 | 问题描述 | 解决方案 | 状态 | | 日期 | 问题描述 | 解决方案 | 状态 |
|------|----------|---------|------| |------|----------|---------|------|
| - | - | - | - | | 2026-03-28 | 'Image' object is not subscriptable | 使用属性访问 | ✅ 已解决 |
| 2026-03-28 | 'Image' object has no attribute 'bytes' | 使用as_bytes()方法 | ✅ 已解决 |
| 2026-03-28 | 'Image' object has no attribute 'as_bytes' | 使用open().read()方法 | ✅ 已解决 |
| 2026-03-28 | 'closing' object has no attribute 'read' | 使用with语句管理 | ✅ 已解决 |
| 2026-03-28 | 没有日志输出 | 添加StreamHandler | ✅ 已解决 |
### 10.2 变更记录 ### 7.2 变更记录
| 日期 | 变更内容 | 变更原因 | 状态 | | 日期 | 变更内容 | 变更原因 | 状态 |
|------|----------|----------|------| |------|----------|----------|------|
| - | - | - | - | | 2026-03-28 | 从Spire.Doc改为mammoth | Spire.Doc兼容性问题 | ✅ 完成 |
| 2026-03-28 | 添加图片验证和保存功能 | 便于调试和验证 | ✅ 完成 |
| 2026-03-28 | 添加详细接口日志 | 便于问题排查 | ✅ 完成 |
### 7.3 文件修改记录
| 文件 | 修改内容 | 日期 |
|------|----------|------|
| `src/erp_uploader.py` | 新建,包含所有ERP上传功能 | 2026-03-28 |
| `src/config.py` | 添加ERP配置和图片处理配置 | 2026-03-28 |
| `src/cli.py` | 添加ERP上传提示 | 2026-03-28 |
| `src/gui.py` | 添加ERP上传功能 | 2026-03-28 |
| `requirements.txt` | 添加mammoth和Pillow | 2026-03-28 |
---
## 8. 参考文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- ERP对接PRD: `Docs/PRD/自动化生成功能测试报告/ERP对接PRD/PRD_测试列表_测试报告_例子说明.md`
--- ---
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论