新增代码
This commit is contained in:
0
difyPlugin/app/services/workcase/__init__.py
Normal file
0
difyPlugin/app/services/workcase/__init__.py
Normal file
298
difyPlugin/app/services/workcase/qrcode/QrCode.py
Normal file
298
difyPlugin/app/services/workcase/qrcode/QrCode.py
Normal file
@@ -0,0 +1,298 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""二维码处理核心类 - 基于 OpenCV QRCodeDetector
|
||||
|
||||
本模块使用 OpenCV 的 QRCodeDetector 进行二维码识别,
|
||||
配合多种图像预处理策略,确保高识别率和跨平台兼容性。
|
||||
"""
|
||||
import base64
|
||||
import io
|
||||
from typing import Optional, Callable, Tuple
|
||||
|
||||
import cv2
|
||||
import httpx
|
||||
import numpy as np
|
||||
import qrcode
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class QRCodeProcessor:
|
||||
"""二维码处理器 - 负责二维码的生成、解析和图像预处理"""
|
||||
|
||||
# 预处理策略映射
|
||||
PREPROCESSING_STRATEGIES = {
|
||||
"original": ("原图", lambda img, gray: img),
|
||||
"grayscale": ("灰度图", lambda img, gray: cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)),
|
||||
"clahe": ("CLAHE增强", lambda img, gray: cv2.cvtColor(
|
||||
cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)).apply(gray),
|
||||
cv2.COLOR_GRAY2BGR
|
||||
)),
|
||||
"adaptive_threshold": ("自适应二值化", lambda img, gray: cv2.cvtColor(
|
||||
cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2),
|
||||
cv2.COLOR_GRAY2BGR
|
||||
)),
|
||||
"otsu": ("Otsu二值化", lambda img, gray: cv2.cvtColor(
|
||||
cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1],
|
||||
cv2.COLOR_GRAY2BGR
|
||||
)),
|
||||
"denoise": ("去噪+二值化", lambda img, gray: cv2.cvtColor(
|
||||
cv2.threshold(
|
||||
cv2.fastNlMeansDenoising(gray, None, 10, 7, 21),
|
||||
0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU
|
||||
)[1],
|
||||
cv2.COLOR_GRAY2BGR
|
||||
)),
|
||||
"sharpen": ("锐化", lambda img, gray: cv2.cvtColor(
|
||||
cv2.filter2D(gray, -1, np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])),
|
||||
cv2.COLOR_GRAY2BGR
|
||||
)),
|
||||
"morphology": ("形态学处理", lambda img, gray: cv2.cvtColor(
|
||||
cv2.morphologyEx(
|
||||
cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2),
|
||||
cv2.MORPH_CLOSE,
|
||||
cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
|
||||
),
|
||||
cv2.COLOR_GRAY2BGR
|
||||
)),
|
||||
"scale_0.5": ("0.5x缩放", lambda img, gray: cv2.resize(
|
||||
img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA
|
||||
)),
|
||||
"scale_1.5": ("1.5x缩放", lambda img, gray: cv2.resize(
|
||||
img, None, fx=1.5, fy=1.5, interpolation=cv2.INTER_CUBIC
|
||||
)),
|
||||
"scale_2.0": ("2.0x缩放", lambda img, gray: cv2.resize(
|
||||
img, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC
|
||||
)),
|
||||
}
|
||||
|
||||
# 策略组合映射
|
||||
STRATEGY_MAP = {
|
||||
"basic": ["original", "grayscale"],
|
||||
"enhanced": ["original", "grayscale", "clahe", "adaptive_threshold", "otsu", "denoise", "sharpen", "morphology"],
|
||||
"all": ["original", "grayscale", "clahe", "adaptive_threshold", "otsu", "denoise", "sharpen", "morphology", "scale_0.5", "scale_1.5", "scale_2.0"],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def generate(
|
||||
content: str,
|
||||
size: int = 300,
|
||||
error_correction: str = "H",
|
||||
box_size: int = 10,
|
||||
border: int = 4
|
||||
) -> str:
|
||||
"""
|
||||
生成二维码
|
||||
|
||||
Args:
|
||||
content: 二维码内容
|
||||
size: 图片大小(像素)
|
||||
error_correction: 纠错级别 (L/M/Q/H)
|
||||
box_size: 每个格子的像素大小
|
||||
border: 边框大小(格子数)
|
||||
|
||||
Returns:
|
||||
base64编码的图片数据 (data:image/png;base64,...)
|
||||
|
||||
Raises:
|
||||
ValueError: 参数错误时抛出异常
|
||||
"""
|
||||
# 验证纠错级别
|
||||
error_levels = {
|
||||
"L": qrcode.constants.ERROR_CORRECT_L, # 7% 容错
|
||||
"M": qrcode.constants.ERROR_CORRECT_M, # 15% 容错
|
||||
"Q": qrcode.constants.ERROR_CORRECT_Q, # 25% 容错
|
||||
"H": qrcode.constants.ERROR_CORRECT_H, # 30% 容错
|
||||
}
|
||||
|
||||
if error_correction not in error_levels:
|
||||
raise ValueError(f"无效的纠错级别: {error_correction},支持: L/M/Q/H")
|
||||
|
||||
# 创建二维码对象
|
||||
qr = qrcode.QRCode(
|
||||
version=None, # 自动确定版本
|
||||
error_correction=error_levels[error_correction],
|
||||
box_size=box_size,
|
||||
border=border,
|
||||
)
|
||||
|
||||
# 添加数据并生成
|
||||
qr.add_data(content)
|
||||
qr.make(fit=True)
|
||||
|
||||
# 生成图片
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
# 调整到指定大小
|
||||
img = img.resize((size, size), Image.Resampling.LANCZOS)
|
||||
|
||||
# 转换为base64
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="PNG")
|
||||
img_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||
|
||||
return f"data:image/png;base64,{img_base64}"
|
||||
|
||||
@staticmethod
|
||||
async def load_image(image_source: str) -> np.ndarray:
|
||||
"""
|
||||
加载图片(支持URL、base64、本地路径)
|
||||
|
||||
Args:
|
||||
image_source: 图片来源
|
||||
- URL: http://... 或 https://...
|
||||
- base64: data:image/...;base64,...
|
||||
- 本地路径: /path/to/image.png
|
||||
|
||||
Returns:
|
||||
OpenCV图片对象 (BGR格式)
|
||||
|
||||
Raises:
|
||||
ValueError: 图片加载失败时抛出异常
|
||||
"""
|
||||
try:
|
||||
# 检查是否为base64
|
||||
if image_source.startswith("data:image"):
|
||||
# 提取base64数据
|
||||
base64_data = image_source.split(",")[1]
|
||||
img_data = base64.b64decode(base64_data)
|
||||
img_array = np.frombuffer(img_data, np.uint8)
|
||||
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
|
||||
|
||||
elif image_source.startswith("http://") or image_source.startswith("https://"):
|
||||
# 下载图片
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
response = await client.get(image_source)
|
||||
response.raise_for_status()
|
||||
img_array = np.frombuffer(response.content, np.uint8)
|
||||
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
|
||||
|
||||
else:
|
||||
# 本地文件
|
||||
img = cv2.imread(image_source)
|
||||
|
||||
if img is None:
|
||||
raise ValueError("无法解析图片数据")
|
||||
|
||||
return img
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"图片加载失败: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def search_qrcode(img: np.ndarray)-> list[np.ndarray]:
|
||||
"""
|
||||
搜索二维码,只搜索不解析
|
||||
|
||||
Args:
|
||||
img: OpenCV图像对象
|
||||
|
||||
Returns:
|
||||
二维码列表
|
||||
"""
|
||||
detector = cv2.QRCodeDetector()
|
||||
imgs = detector.detect(img)
|
||||
|
||||
@staticmethod
|
||||
def decode(img: np.ndarray) -> Optional[str]:
|
||||
"""
|
||||
使用 OpenCV QRCodeDetector 解码二维码
|
||||
|
||||
Args:
|
||||
img: OpenCV图像对象
|
||||
|
||||
Returns:
|
||||
二维码内容,如果没有检测到返回None
|
||||
"""
|
||||
detector = cv2.QRCodeDetector()
|
||||
data, bbox, _ = detector.detectAndDecode(img)
|
||||
|
||||
if data:
|
||||
return data
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
async def parse(
|
||||
image_source: str,
|
||||
strategy: str = "auto"
|
||||
) -> dict:
|
||||
"""
|
||||
解析二维码(使用 OpenCV + 按需预处理策略)
|
||||
|
||||
解码策略:
|
||||
1. 根据 strategy 参数选择预处理步骤列表
|
||||
2. 按需应用每种预处理算法并立即尝试解码
|
||||
3. 一旦成功立即返回,避免不必要的计算
|
||||
|
||||
Args:
|
||||
image_source: 图片来源(URL/base64/本地路径)
|
||||
strategy: 预处理策略
|
||||
- basic: 原图 + 灰度图(2种)
|
||||
- enhanced: basic + 6种增强算法(8种)
|
||||
- all: auto + 多尺度(11种)
|
||||
|
||||
Returns:
|
||||
解析结果字典:
|
||||
{
|
||||
"success": bool,
|
||||
"content": str or None,
|
||||
"strategy_used": str, # 使用的预处理策略名称
|
||||
"preprocessing_index": int, # 预处理索引
|
||||
"total_attempts": int,
|
||||
"message": str # 仅失败时有
|
||||
}
|
||||
"""
|
||||
# 验证策略参数
|
||||
if strategy not in QRCodeProcessor.STRATEGY_MAP:
|
||||
raise ValueError(f"无效的策略: {strategy},支持: {list(QRCodeProcessor.STRATEGY_MAP.keys())}")
|
||||
|
||||
# 加载原始图片
|
||||
img = await QRCodeProcessor.load_image(image_source)
|
||||
|
||||
# 预先生成灰度图(很多预处理都需要)
|
||||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# 获取该策略对应的预处理步骤列表
|
||||
preprocessing_steps = QRCodeProcessor.STRATEGY_MAP[strategy]
|
||||
|
||||
# 依次应用每种预处理并尝试解码
|
||||
for idx, step_key in enumerate(preprocessing_steps):
|
||||
strategy_name, preprocess_func = QRCodeProcessor.PREPROCESSING_STRATEGIES[step_key]
|
||||
|
||||
try:
|
||||
# 按需处理图像
|
||||
processed_img = preprocess_func(img, gray)
|
||||
|
||||
# 立即尝试解码
|
||||
result = QRCodeProcessor.decode(processed_img)
|
||||
|
||||
if result:
|
||||
# 解码成功,立即返回
|
||||
return {
|
||||
"success": True,
|
||||
"content": result,
|
||||
"strategy_used": f"opencv_{strategy_name}",
|
||||
"preprocessing_index": idx,
|
||||
"total_attempts": idx + 1
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
# 某个预处理步骤失败,继续尝试下一个
|
||||
continue
|
||||
|
||||
# 所有预处理方法都失败
|
||||
return {
|
||||
"success": False,
|
||||
"content": None,
|
||||
"message": f"未检测到二维码或二维码损坏(已尝试 {len(preprocessing_steps)} 种预处理)",
|
||||
"total_attempts": len(preprocessing_steps)
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
# 示例用法
|
||||
result = await QRCodeProcessor.parse("F:/Project/urbanLifeline/docs/qrcode.png", "enhanced")
|
||||
print(result)
|
||||
|
||||
asyncio.run(main())
|
||||
201
difyPlugin/app/services/workcase/qrcode/QrCodeService.py
Normal file
201
difyPlugin/app/services/workcase/qrcode/QrCodeService.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""二维码服务层 - 提供统一的业务逻辑接口"""
|
||||
import base64
|
||||
from typing import Optional
|
||||
|
||||
from .QrCode import QRCodeProcessor
|
||||
|
||||
|
||||
class QrCodeService:
|
||||
"""二维码服务 - 业务逻辑层"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化服务"""
|
||||
self.processor = QRCodeProcessor()
|
||||
|
||||
async def generate_qrcode(
|
||||
self,
|
||||
content: str,
|
||||
size: int = 300,
|
||||
error_correction: str = "H"
|
||||
) -> dict:
|
||||
"""
|
||||
生成二维码
|
||||
|
||||
Args:
|
||||
content: 二维码内容
|
||||
size: 图片大小(像素)
|
||||
error_correction: 纠错级别 (L/M/Q/H)
|
||||
|
||||
Returns:
|
||||
{
|
||||
"success": bool,
|
||||
"image": str, # base64编码的图片
|
||||
"content": str,
|
||||
"size": int,
|
||||
"error_correction": str,
|
||||
"error": str # 仅失败时有
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# 验证参数
|
||||
if not content:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "内容不能为空"
|
||||
}
|
||||
|
||||
if size < 100 or size > 2000:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "尺寸必须在100-2000之间"
|
||||
}
|
||||
|
||||
if error_correction not in ["L", "M", "Q", "H"]:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "纠错级别必须是 L/M/Q/H 之一"
|
||||
}
|
||||
|
||||
# 生成二维码
|
||||
img_base64 = self.processor.generate(
|
||||
content=content,
|
||||
size=size,
|
||||
error_correction=error_correction
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"image": img_base64,
|
||||
"content": content,
|
||||
"size": size,
|
||||
"error_correction": error_correction
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"生成失败: {str(e)}"
|
||||
}
|
||||
|
||||
async def parse_qrcode(
|
||||
self,
|
||||
image_source: str,
|
||||
strategy: str = "auto"
|
||||
) -> dict:
|
||||
"""
|
||||
解析二维码
|
||||
|
||||
Args:
|
||||
image_source: 图片来源(URL/base64/本地路径)
|
||||
strategy: 预处理策略 (basic/auto/enhanced/all)
|
||||
|
||||
Returns:
|
||||
{
|
||||
"success": bool,
|
||||
"content": str or None,
|
||||
"strategy_used": str,
|
||||
"total_attempts": int,
|
||||
"error": str # 仅失败时有
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# 验证参数
|
||||
if not image_source:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "图片来源不能为空"
|
||||
}
|
||||
|
||||
if strategy not in ["basic", "auto", "enhanced", "all"]:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "策略必须是 basic/auto/enhanced/all 之一"
|
||||
}
|
||||
|
||||
# 解析二维码
|
||||
result = await self.processor.parse(image_source, strategy)
|
||||
|
||||
if result["success"]:
|
||||
return result
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"content": None,
|
||||
"error": result.get("message", "解析失败"),
|
||||
"total_attempts": result.get("total_attempts", 0)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"content": None,
|
||||
"error": f"解析失败: {str(e)}"
|
||||
}
|
||||
|
||||
async def parse_qrcode_from_file(
|
||||
self,
|
||||
file_content: bytes,
|
||||
file_type: str = "png",
|
||||
strategy: str = "auto"
|
||||
) -> dict:
|
||||
"""
|
||||
从文件内容解析二维码
|
||||
|
||||
Args:
|
||||
file_content: 文件二进制内容
|
||||
file_type: 文件类型 (png/jpg/jpeg等)
|
||||
strategy: 预处理策略
|
||||
|
||||
Returns:
|
||||
解析结果(格式同parse_qrcode)
|
||||
"""
|
||||
try:
|
||||
# 转换为base64
|
||||
img_base64 = base64.b64encode(file_content).decode()
|
||||
image_source = f"data:image/{file_type};base64,{img_base64}"
|
||||
|
||||
# 调用解析方法
|
||||
return await self.parse_qrcode(image_source, strategy)
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"content": None,
|
||||
"error": f"文件解析失败: {str(e)}"
|
||||
}
|
||||
|
||||
def validate_qrcode_content(self, content: str, max_length: int = 2953) -> dict:
|
||||
"""
|
||||
验证二维码内容是否合法
|
||||
|
||||
Args:
|
||||
content: 要验证的内容
|
||||
max_length: 最大长度(默认2953字节,version 40 with L级别)
|
||||
|
||||
Returns:
|
||||
{
|
||||
"valid": bool,
|
||||
"length": int,
|
||||
"error": str # 仅无效时有
|
||||
}
|
||||
"""
|
||||
if not content:
|
||||
return {
|
||||
"valid": False,
|
||||
"error": "内容不能为空"
|
||||
}
|
||||
|
||||
content_bytes = content.encode("utf-8")
|
||||
length = len(content_bytes)
|
||||
|
||||
if length > max_length:
|
||||
return {
|
||||
"valid": False,
|
||||
"length": length,
|
||||
"error": f"内容过长,当前{length}字节,最大支持{max_length}字节"
|
||||
}
|
||||
|
||||
return {
|
||||
"valid": True,
|
||||
"length": length
|
||||
}
|
||||
4
difyPlugin/app/services/workcase/qrcode/__init__.py
Normal file
4
difyPlugin/app/services/workcase/qrcode/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""二维码服务模块"""
|
||||
from .QrCodeService import QrCodeService
|
||||
|
||||
__all__ = ["QrCodeService"]
|
||||
324
difyPlugin/app/services/workcase/qrcode/test_qrcode.py
Normal file
324
difyPlugin/app/services/workcase/qrcode/test_qrcode.py
Normal file
@@ -0,0 +1,324 @@
|
||||
"""二维码服务测试脚本
|
||||
|
||||
使用方法:
|
||||
python test_qrcode.py
|
||||
|
||||
测试内容:
|
||||
1. 生成二维码
|
||||
2. 解析生成的二维码
|
||||
3. 测试不同的预处理策略
|
||||
4. 测试错误处理
|
||||
"""
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from app.services.workcase.qrcode import QrCodeService
|
||||
|
||||
|
||||
class QRCodeTester:
|
||||
"""二维码服务测试类"""
|
||||
|
||||
def __init__(self):
|
||||
self.service = QrCodeService()
|
||||
self.test_results = []
|
||||
|
||||
def print_header(self, title: str):
|
||||
"""打印测试标题"""
|
||||
print("\n" + "=" * 60)
|
||||
print(f" {title}")
|
||||
print("=" * 60)
|
||||
|
||||
def print_result(self, test_name: str, success: bool, details: str = ""):
|
||||
"""打印测试结果"""
|
||||
status = "✓ 通过" if success else "✗ 失败"
|
||||
print(f"\n{status} - {test_name}")
|
||||
if details:
|
||||
print(f" 详情: {details}")
|
||||
self.test_results.append((test_name, success))
|
||||
|
||||
async def test_generate_qrcode(self):
|
||||
"""测试生成二维码"""
|
||||
self.print_header("测试1: 生成二维码")
|
||||
|
||||
# 测试1.1: 基本生成
|
||||
result = await self.service.generate_qrcode(
|
||||
content="https://github.com",
|
||||
size=300,
|
||||
error_correction="H"
|
||||
)
|
||||
|
||||
if result["success"] and "image" in result:
|
||||
self.print_result(
|
||||
"1.1 基本生成",
|
||||
True,
|
||||
f"内容长度: {len(result['image'])} 字符"
|
||||
)
|
||||
# 保存生成的图片用于后续测试
|
||||
self.generated_image = result["image"]
|
||||
else:
|
||||
self.print_result("1.1 基本生成", False, result.get("error", "未知错误"))
|
||||
self.generated_image = None
|
||||
|
||||
# 测试1.2: 不同纠错级别
|
||||
for level in ["L", "M", "Q", "H"]:
|
||||
result = await self.service.generate_qrcode(
|
||||
content="测试内容",
|
||||
size=200,
|
||||
error_correction=level
|
||||
)
|
||||
self.print_result(
|
||||
f"1.2 纠错级别 {level}",
|
||||
result["success"],
|
||||
f"图片大小: {result.get('size', 'N/A')}"
|
||||
)
|
||||
|
||||
# 测试1.3: 参数验证
|
||||
result = await self.service.generate_qrcode(
|
||||
content="",
|
||||
size=300,
|
||||
error_correction="H"
|
||||
)
|
||||
self.print_result(
|
||||
"1.3 空内容验证",
|
||||
not result["success"],
|
||||
result.get("error", "")
|
||||
)
|
||||
|
||||
# 测试1.4: 无效尺寸
|
||||
result = await self.service.generate_qrcode(
|
||||
content="test",
|
||||
size=50, # 太小
|
||||
error_correction="H"
|
||||
)
|
||||
self.print_result(
|
||||
"1.4 无效尺寸验证",
|
||||
not result["success"],
|
||||
result.get("error", "")
|
||||
)
|
||||
|
||||
async def test_parse_qrcode(self):
|
||||
"""测试解析二维码"""
|
||||
self.print_header("测试2: 解析二维码")
|
||||
|
||||
if not self.generated_image:
|
||||
print("⚠ 跳过解析测试(没有生成的图片)")
|
||||
return
|
||||
|
||||
# 测试2.1: 解析自己生成的二维码
|
||||
result = await self.service.parse_qrcode(
|
||||
image_source=self.generated_image,
|
||||
strategy="auto"
|
||||
)
|
||||
|
||||
if result["success"]:
|
||||
self.print_result(
|
||||
"2.1 解析生成的二维码",
|
||||
True,
|
||||
f"内容: {result['content']}, 尝试次数: {result.get('total_attempts', 0)}"
|
||||
)
|
||||
else:
|
||||
self.print_result(
|
||||
"2.1 解析生成的二维码",
|
||||
False,
|
||||
result.get("error", "未知错误")
|
||||
)
|
||||
|
||||
# 测试2.2: 测试不同策略
|
||||
for strategy in ["basic", "auto", "enhanced"]:
|
||||
result = await self.service.parse_qrcode(
|
||||
image_source=self.generated_image,
|
||||
strategy=strategy
|
||||
)
|
||||
self.print_result(
|
||||
f"2.2 策略 {strategy}",
|
||||
result["success"],
|
||||
f"尝试次数: {result.get('total_attempts', 0)}"
|
||||
)
|
||||
|
||||
# 测试2.3: 无效输入
|
||||
result = await self.service.parse_qrcode(
|
||||
image_source="",
|
||||
strategy="auto"
|
||||
)
|
||||
self.print_result(
|
||||
"2.3 空图片源验证",
|
||||
not result["success"],
|
||||
result.get("error", "")
|
||||
)
|
||||
|
||||
async def test_validate_content(self):
|
||||
"""测试内容验证"""
|
||||
self.print_header("测试3: 内容验证")
|
||||
|
||||
# 测试3.1: 正常内容
|
||||
result = self.service.validate_qrcode_content("https://example.com")
|
||||
self.print_result(
|
||||
"3.1 正常内容",
|
||||
result["valid"],
|
||||
f"长度: {result.get('length', 0)} 字节"
|
||||
)
|
||||
|
||||
# 测试3.2: 空内容
|
||||
result = self.service.validate_qrcode_content("")
|
||||
self.print_result(
|
||||
"3.2 空内容",
|
||||
not result["valid"],
|
||||
result.get("error", "")
|
||||
)
|
||||
|
||||
# 测试3.3: 超长内容
|
||||
long_content = "a" * 3000
|
||||
result = self.service.validate_qrcode_content(long_content)
|
||||
self.print_result(
|
||||
"3.3 超长内容",
|
||||
not result["valid"],
|
||||
result.get("error", "")
|
||||
)
|
||||
|
||||
# 测试3.4: 中文内容
|
||||
result = self.service.validate_qrcode_content("这是一段中文测试内容")
|
||||
self.print_result(
|
||||
"3.4 中文内容",
|
||||
result["valid"],
|
||||
f"长度: {result.get('length', 0)} 字节"
|
||||
)
|
||||
|
||||
async def test_integration(self):
|
||||
"""集成测试:生成 -> 解析 -> 验证"""
|
||||
self.print_header("测试4: 集成测试")
|
||||
|
||||
test_contents = [
|
||||
"https://github.com",
|
||||
"简单文本",
|
||||
"{'key': 'value', 'number': 123}", # JSON
|
||||
"mailto:test@example.com",
|
||||
"tel:+86-123-4567-8900"
|
||||
]
|
||||
|
||||
for idx, content in enumerate(test_contents, 1):
|
||||
# 生成
|
||||
gen_result = await self.service.generate_qrcode(
|
||||
content=content,
|
||||
size=300,
|
||||
error_correction="H"
|
||||
)
|
||||
|
||||
if not gen_result["success"]:
|
||||
self.print_result(
|
||||
f"4.{idx} 集成测试: {content[:20]}...",
|
||||
False,
|
||||
"生成失败"
|
||||
)
|
||||
continue
|
||||
|
||||
# 解析
|
||||
parse_result = await self.service.parse_qrcode(
|
||||
image_source=gen_result["image"],
|
||||
strategy="auto"
|
||||
)
|
||||
|
||||
# 验证内容是否一致
|
||||
success = (
|
||||
parse_result["success"] and
|
||||
parse_result.get("content") == content
|
||||
)
|
||||
|
||||
self.print_result(
|
||||
f"4.{idx} {content[:30]}",
|
||||
success,
|
||||
f"原始: {content[:20]}... | 解析: {parse_result.get('content', '')[:20]}..."
|
||||
)
|
||||
|
||||
async def test_error_handling(self):
|
||||
"""测试错误处理"""
|
||||
self.print_header("测试5: 错误处理")
|
||||
|
||||
# 测试5.1: 无效的图片URL
|
||||
result = await self.service.parse_qrcode(
|
||||
image_source="https://invalid-url-that-does-not-exist.com/image.png",
|
||||
strategy="auto"
|
||||
)
|
||||
self.print_result(
|
||||
"5.1 无效URL处理",
|
||||
not result["success"],
|
||||
result.get("error", "")[:50]
|
||||
)
|
||||
|
||||
# 测试5.2: 无效的base64
|
||||
result = await self.service.parse_qrcode(
|
||||
image_source="data:image/png;base64,invalid_base64",
|
||||
strategy="auto"
|
||||
)
|
||||
self.print_result(
|
||||
"5.2 无效base64处理",
|
||||
not result["success"],
|
||||
result.get("error", "")[:50]
|
||||
)
|
||||
|
||||
# 测试5.3: 无效的纠错级别
|
||||
result = await self.service.generate_qrcode(
|
||||
content="test",
|
||||
size=300,
|
||||
error_correction="X" # 无效
|
||||
)
|
||||
self.print_result(
|
||||
"5.3 无效纠错级别",
|
||||
not result["success"],
|
||||
result.get("error", "")
|
||||
)
|
||||
|
||||
def print_summary(self):
|
||||
"""打印测试总结"""
|
||||
self.print_header("测试总结")
|
||||
|
||||
total = len(self.test_results)
|
||||
passed = sum(1 for _, success in self.test_results if success)
|
||||
failed = total - passed
|
||||
|
||||
print(f"\n总测试数: {total}")
|
||||
print(f"通过: {passed} (✓)")
|
||||
print(f"失败: {failed} (✗)")
|
||||
print(f"成功率: {passed/total*100:.1f}%\n")
|
||||
|
||||
if failed > 0:
|
||||
print("失败的测试:")
|
||||
for name, success in self.test_results:
|
||||
if not success:
|
||||
print(f" - {name}")
|
||||
|
||||
async def run_all_tests(self):
|
||||
"""运行所有测试"""
|
||||
print("\n" + "=" * 60)
|
||||
print(" 二维码服务测试套件")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
await self.test_generate_qrcode()
|
||||
await self.test_parse_qrcode()
|
||||
await self.test_validate_content()
|
||||
await self.test_integration()
|
||||
await self.test_error_handling()
|
||||
|
||||
self.print_summary()
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试过程中出现错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
tester = QRCodeTester()
|
||||
await tester.run_all_tests()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 运行测试
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user