1. 企业级接口自动化测试框架概述
在当今快速迭代的软件开发环境中,接口自动化测试已成为保障产品质量的关键环节。作为一名经历过多个企业级项目的测试架构师,我深刻理解一个优秀的自动化测试框架需要具备哪些特质。不同于个人项目或小型团队的简易方案,企业级框架必须兼顾可维护性、扩展性和团队协作效率。
企业级框架的核心价值在于:当你的团队从5人扩展到50人,当你的接口数量从几十个增长到上千个,当你的测试环境从单一环境扩展到多套隔离环境时,框架依然能够稳定运行,而不是陷入维护噩梦。这也是为什么我们需要从一开始就采用高标准来构建框架。
Python生态因其丰富的测试库和简洁的语法,成为接口自动化测试的首选。requests库的HTTP请求处理能力,pytest框架的灵活性和扩展性,再加上allure报告的直观展示,构成了一个强大而稳定的技术栈基础。这套组合在企业环境中经过反复验证,能够平衡开发效率与运行稳定性。
2. 框架设计原则与技术选型
2.1 企业级框架的七大核心目标
在设计框架之初,我们确立了七个必须实现的核心目标,这些目标直接决定了技术选型和架构设计:
-
环境隔离:支持dev/test/prod环境无缝切换,避免硬编码环境配置。我曾见过一个项目因为环境配置混乱,导致测试脚本误删生产数据,损失惨重。
-
数据与用例分离:测试数据独立存储,便于维护和批量更新。当业务规则变更时,你只需要修改数据文件,而不必翻遍所有测试用例。
-
完整日志追溯:详细的执行日志不仅帮助调试,更是企业审计的必要条件。我们曾凭借完整的日志链,快速定位了一个由中间件变更引起的偶发故障。
-
美观的测试报告:allure报告支持步骤截图、日志关联和自定义描述,让非技术干系人也能理解测试结果。
-
强扩展性:新增接口或功能时,只需添加用例而不必修改框架。在一个电商项目中,我们仅用2天就接入了新开发的支付模块。
-
CI/CD集成:与Jenkins等工具无缝对接,实现自动化触发和结果推送。这是持续交付流水线中不可或缺的一环。
-
容错机制:失败重试、敏感数据脱敏和异常捕获,确保测试稳定运行不泄露敏感信息。
2.2 技术栈选型的深度考量
我们的技术选型经过了严格的POC验证和性能测试,以下是每个组件的选型理由:
开发语言:Python 3.9+。相比Java,Python的语法更简洁,学习曲线平缓;相比Go,Python的测试生态更成熟。在企业环境中,降低团队学习成本至关重要。
HTTP客户端:requests库。虽然urllib3更底层,aiohttp支持异步,但requests的API设计最为优雅,稳定性经过十多年的验证。我们的压力测试显示,在1000QPS下requests仍能稳定工作。
测试框架:pytest。它比unittest更灵活,比robotframework更轻量。pytest的夹具系统和插件机制,让我们可以轻松实现依赖注入和功能扩展。
配置管理:YAML格式。相比JSON,YAML支持注释且可读性更好;相比INI,YAML支持复杂数据结构。PyYAML库的safe_load方法还能有效防止YAML注入攻击。
测试数据:JSON+Excel混合策略。JSON用于结构化数据,Excel则方便测试人员维护。openpyxl库处理Excel的性能比pandas更轻量,特别适合测试场景。
日志系统:Python原生logging。无需引入ELK等重型方案,通过合理配置就能满足大多数企业的审计需求。我们实现了按日期滚动日志,单个日志文件不超过50MB。
测试报告:allure-pytest。它生成的HTML报告支持步骤层级展示、附件添加和历史趋势分析。在向管理层汇报时,这种可视化展示比原始日志更有说服力。
增强工具:
- pytest-dependency:解决用例间的依赖关系
- tenacity:实现智能重试策略(指数退避)
- pycryptodome:处理接口加密和数据脱敏
3. 环境准备与项目初始化
3.1 虚拟环境搭建最佳实践
Python项目的依赖隔离是避免"它在我的机器上能运行"问题的第一道防线。以下是经过优化的虚拟环境操作流程:
bash复制# 创建虚拟环境(推荐使用venv模块而非virtualenv)
python -m venv venv --prompt "api_auto"
# 激活环境
# Windows
venv\Scripts\activate
# MacOS/Linux
source venv/bin/activate
# 设置pip镜像源(国内环境加速)
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
# 安装基础依赖
pip install pip==23.3.1 --upgrade
特别建议在创建虚拟环境时添加--prompt参数,这会在命令行提示符中显示环境名称,避免误操作。我们曾经因为工程师在错误的环境中安装依赖,导致测试结果不一致,排查了整整一天。
3.2 依赖管理的进阶技巧
requirements.txt的局限性在于无法精确控制次级依赖的版本。在实际项目中,我推荐使用pip-tools进行更精细的依赖管理:
bash复制# 安装pip-tools
pip install pip-tools
# 创建requirements.in文件(只声明直接依赖)
echo """
requests>=2.31.0
pytest>=7.4.0
PyYAML>=6.0.1
allure-pytest>=2.13.2
openpyxl>=3.1.2
pytest-dependency>=0.5.1
tenacity>=8.2.3
pycryptodome>=3.19.0
""" > requirements.in
# 生成锁定版本的requirements.txt
pip-compile --generate-hashes --output-file=requirements.txt requirements.in
# 安装时校验哈希值
pip install -r requirements.txt --require-hashes
这种方式的优势在于:
- 锁定所有依赖的确切版本和哈希值,确保环境一致性
- 当需要更新依赖时,只需修改
.in文件并重新compile - 哈希校验可以防止供应链攻击
3.3 项目目录结构的艺术
良好的目录结构是项目可维护性的基础。我们的设计遵循以下原则:
- 按功能而非类型划分(传统MVC模式不适合测试框架)
- 公共组件放在common目录,避免循环引用
- 测试数据与用例分离,便于数据驱动测试
- 环境配置集中管理,支持多环境切换
经过多个项目的迭代,最终形成的目录结构如下:
code复制enterprise_api_auto/
├── config/ # 配置中心
│ ├── env.yaml # 多环境配置
│ ├── api_config.yaml # 接口路径配置
│ └── log_config.py # 日志配置
├── test_case/ # 用例按业务模块划分
│ ├── test_user_module/
│ └── test_order_module/
├── test_data/ # 测试数据仓库
│ ├── user_data.json
│ └── order_data.xlsx
├── common/ # 核心框架代码
│ ├── http_client.py
│ ├── config_reader.py
│ └── assert_tool.py
├── report/ # 测试报告输出
├── log/ # 执行日志
├── script/ # 运维脚本
├── .gitignore # 版本控制忽略规则
├── pytest.ini # pytest配置
└── README.md # 项目文档
关键细节:
- 每个Python包都必须包含
__init__.py文件,即使是空文件 - 日志和报告目录应在
.gitignore中排除,避免提交大文件 README.md应包含环境搭建指南和快速开始说明
4. 核心组件实现与设计原理
4.1 配置管理的工程化实践
4.1.1 环境配置设计
config/env.yaml采用分层设计,支持不同环境的差异化配置:
yaml复制dev:
base_url: "https://dev-api.example.com"
timeout: 5 # 较短的超时时间,快速失败
retry_times: 1
db_check: false # 开发环境不启用数据库校验
test:
base_url: "https://test-api.example.com"
timeout: 10
retry_times: 2
db_check: true
db_host: "test-db.example.com"
prod:
base_url: "https://api.example.com"
timeout: 15 # 生产环境容忍更长的响应时间
retry_times: 3
db_check: true
db_host: "prod-db.example.com"
enable_alert: true # 生产环境启用告警
4.1.2 配置读取器的安全实现
config_reader.py采用单例模式避免重复读取文件,并增加了类型检查和环境变量覆盖:
python复制import os
from typing import Dict, Any
import yaml
from dotenv import load_dotenv
class ConfigReader:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._config = {}
load_dotenv() # 加载.env文件
cls._instance._load_configs()
return cls._instance
def _load_configs(self):
"""加载所有配置文件并合并环境变量"""
self._load_from_yaml("config/env.yaml", "env")
self._load_from_yaml("config/api_config.yaml", "api")
def _load_from_yaml(self, file_path: str, config_key: str):
"""从YAML文件加载配置,并应用环境变量覆盖"""
try:
with open(file_path, "r") as f:
config = yaml.safe_load(f) or {}
# 环境变量覆盖 (格式: CONFIG_ENV_base_url)
prefix = f"CONFIG_{config_key.upper()}_"
for env_key, env_value in os.environ.items():
if env_key.startswith(prefix):
yaml_key = env_key[len(prefix):].lower()
keys = yaml_key.split('__') # 使用双下划线表示嵌套路径
self._nested_set(config, keys, env_value)
self._config[config_key] = config
except Exception as e:
raise RuntimeError(f"加载配置文件{file_path}失败: {str(e)}")
def _nested_set(self, dic: Dict, keys: List[str], value: Any):
"""递归设置嵌套字典的值"""
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = self._parse_env_value(value)
def _parse_env_value(self, value: str):
"""尝试将环境变量字符串转换为适当类型"""
if value.lower() == 'true':
return True
if value.lower() == 'false':
return False
try:
return int(value)
except ValueError:
try:
return float(value)
except ValueError:
return value
这种设计实现了:
- 配置热加载:修改配置文件后无需重启测试
- 环境变量优先:方便在CI/CD中动态覆盖配置
- 类型自动转换:字符串环境变量转为合适的Python类型
4.2 HTTP客户端的工业级封装
http_client.py的核心价值在于封装了企业测试中的各种边缘情况处理:
python复制from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
retry_if_exception_type
)
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class HttpClient:
def __init__(self, env: str = "test"):
self.config = ConfigReader().get_env_config(env)
self.session = requests.Session()
# 配置请求重试策略
retry_strategy = Retry(
total=self.config.get("retry_times", 3),
backoff_factor=self.config.get("backoff_factor", 1),
status_forcelist=[408, 429, 500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("https://", adapter)
self.session.mount("http://", adapter)
# 设置默认请求头
self.session.headers.update({
"User-Agent": "EnterpriseAPIAuto/1.0",
"Accept": "application/json"
})
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=(
retry_if_exception_type(requests.exceptions.Timeout) |
retry_if_exception_type(requests.exceptions.ConnectionError)
),
reraise=True
)
def _send_request(self, method: str, api_path: str, **kwargs):
"""智能请求发送核心方法"""
full_url = self._build_url(api_path)
# 请求计时
start_time = time.time()
try:
response = self.session.request(
method.upper(),
full_url,
timeout=self.config.get("timeout", 10),
**kwargs
)
response.raise_for_status()
return response
except requests.exceptions.SSLError:
# 处理自签名证书场景
if self.config.get("verify_ssl", True):
raise
kwargs["verify"] = False
return self.session.request(method, full_url, **kwargs)
finally:
duration = time.time() - start_time
self._log_request(method, full_url, duration, kwargs, response)
def _build_url(self, api_path: str) -> str:
"""构建完整URL,支持相对路径和配置键"""
if api_path.startswith(("http://", "https://")):
return api_path
if not api_path.startswith("/"):
api_path = ConfigReader().get_api_path(api_path)
return f"{self.config['base_url']}{api_path}"
def _log_request(self, method: str, url: str, duration: float,
kwargs: dict, response: requests.Response):
"""结构化日志记录"""
log_data = {
"method": method,
"url": url,
"duration": round(duration, 2),
"status_code": getattr(response, "status_code", None),
"request": {
"headers": self._sanitize_headers(kwargs.get("headers", {})),
"params": kwargs.get("params"),
"data": self._sanitize_data(kwargs.get("data")),
"json": self._sanitize_data(kwargs.get("json"))
},
"response": {
"headers": dict(getattr(response, "headers", {})),
"body": self._sanitize_data(
getattr(response, "text", None)
) if self.config.get("log_response_body", True) else "[REDACTED]"
}
}
logger.info("API请求详情", extra={"api_request": log_data})
def _sanitize_headers(self, headers: dict) -> dict:
"""敏感头信息脱敏"""
sensitive_keys = ["authorization", "token", "cookie", "set-cookie"]
return {
k: "[REDACTED]" if k.lower() in sensitive_keys else v
for k, v in headers.items()
}
def _sanitize_data(self, data: Any) -> Any:
"""敏感数据脱敏"""
if isinstance(data, dict):
return {k: self._sanitize_value(k, v) for k, v in data.items()}
if isinstance(data, (list, tuple)):
return [self._sanitize_value(None, x) for x in data]
return self._sanitize_value(None, data)
def _sanitize_value(self, key: Optional[str], value: Any) -> Any:
"""基于字段名的值脱敏"""
if value is None:
return None
if isinstance(value, (int, float, bool)):
return value
str_value = str(value)
sensitive_keys = ["password", "token", "credit_card", "phone"]
if key and any(s in key.lower() for s in sensitive_keys):
return f"[REDACTED_{len(str_value)}]"
return value
这个HTTP客户端实现了:
- 智能重试机制:对网络抖动和5xx错误自动重试
- 连接池管理:通过Session重用TCP连接,提升性能
- 全面的日志记录:结构化记录请求/响应详情
- 敏感信息脱敏:防止密码等敏感数据泄露到日志
- SSL证书灵活性:支持自签名证书场景
- 性能监控:记录每个请求的耗时
5. 测试用例设计与实现
5.1 数据驱动测试的最佳实践
test_data/user_data.json展示了如何结构化测试数据:
json复制{
"login": {
"success": {
"description": "正常登录场景-正确用户名密码",
"username": "test_user",
"password": "P@ssw0rd123",
"expected": {
"status": 200,
"code": 0,
"message": "登录成功",
"has_token": true
}
},
"wrong_password": {
"description": "异常登录场景-错误密码",
"username": "test_user",
"password": "WrongPassword",
"expected": {
"status": 200,
"code": 1001,
"message": "用户名或密码错误",
"has_token": false
}
}
}
}
这种结构化的好处在于:
- 每个测试场景有完整的描述
- 输入和预期输出明确分离
- 支持多层级的测试场景组织
- 易于转换为CSV/Excel格式供非技术人员编辑
5.2 用例编写的工程化模式
test_user_login.py展示了企业级测试用例的标准写法:
python复制import pytest
from common.http_client import HttpClient
from common.assert_tool import AssertTool
from common.data_reader import DataReader
@pytest.fixture(scope="module")
def api_client():
"""模块级夹具:初始化HTTP客户端"""
client = HttpClient(env="test")
yield client
client.close_session()
@pytest.fixture
def login_data(request):
"""参数化夹具:动态加载测试数据"""
data_key = request.param
test_data = DataReader.read_json("user_data.json")
return test_data["login"][data_key]
class TestUserLogin:
"""用户登录接口测试套件"""
@pytest.mark.smoke
@pytest.mark.parametrize(
"login_data",
["success", "wrong_password"],
indirect=True
)
def test_login_scenarios(self, api_client, login_data):
"""登录接口数据驱动测试"""
# 准备请求
payload = {
"username": login_data["username"],
"password": login_data["password"]
}
# 发送请求
response = api_client.post(
"user.login",
json=payload,
headers={"Content-Type": "application/json"}
)
# 验证响应
AssertTool.assert_code(response, login_data["expected"]["status"])
AssertTool.assert_json_value(response, "code", login_data["expected"]["code"])
AssertTool.assert_json_value(response, "message", login_data["expected"]["message"])
if login_data["expected"]["has_token"]:
AssertTool.assert_json_key(response, "data.token")
# 缓存token供后续测试使用
api_client.session.headers.update({
"Authorization": f"Bearer {response.json()['data']['token']}"
})
这种模式的优势在于:
- 使用夹具管理测试资源生命周期
- 参数化实现数据驱动测试
- 清晰的测试步骤注释
- 断言与业务验证分离
- 支持测试上下文传递(如token)
5.3 断言设计的艺术
assert_tool.py扩展了丰富的断言方法:
python复制from typing import Any, Dict, List, Optional
import jsonpath_rw_ext as jp
from deepdiff import DeepDiff
from datetime import datetime
class AssertTool:
"""增强型断言工具"""
@staticmethod
def assert_schema(response, schema: Dict):
"""验证响应体JSON Schema"""
from jsonschema import validate
try:
validate(instance=response.json(), schema=schema)
except Exception as e:
raise AssertionError(f"Schema验证失败: {str(e)}")
@staticmethod
def assert_json_path(response, json_path: str, expected: Any = None):
"""使用JSONPath验证响应内容"""
values = jp.match(json_path, response.json())
if not values:
raise AssertionError(f"JSONPath '{json_path}' 未匹配到任何值")
if expected is not None:
assert values[0] == expected, (
f"JSONPath '{json_path}' 值不匹配\n"
f"预期: {expected}\n"
f"实际: {values[0]}"
)
@staticmethod
def assert_response_time(response, max_time: float):
"""验证响应时间"""
elapsed = response.elapsed.total_seconds()
assert elapsed <= max_time, (
f"响应时间 {elapsed}s 超过最大限制 {max_time}s"
)
@staticmethod
def assert_equals(actual, expected, ignore_keys: List[str] = None):
"""深度比较两个对象,可忽略指定键"""
diff = DeepDiff(
actual,
expected,
exclude_paths=ignore_keys
)
assert not diff, f"对象不匹配:\n{diff.pretty()}"
@staticmethod
def assert_is_date(string: str, fmt: str = "%Y-%m-%d %H:%M:%S"):
"""验证字符串是否为有效日期"""
try:
datetime.strptime(string, fmt)
except ValueError:
raise AssertionError(f"'{string}' 不是有效的日期格式 '{fmt}'")
这些断言方法覆盖了:
- Schema验证:确保接口返回结构符合约定
- JSONPath提取:处理复杂嵌套JSON
- 性能断言:验证响应时间SLA
- 深度比较:忽略不重要的字段差异
- 格式验证:日期、邮箱等特殊格式
6. 持续集成与报告生成
6.1 Allure报告的深度定制
pytest.ini中配置allure报告增强:
ini复制[pytest]
addopts =
-v
--alluredir=report/allure_raw
--clean-alluredir
--env=test
--allure-severities=blocker,critical,normal
--allure-link-pattern=issue:https://jira.example.com/browse/{}
--allure-link-pattern=tms:https://testrail.example.com/index.php?/cases/view/{}
allure_report =
report/allure_html
--clean
--report-dir=report/allure_html
--logo=config/allure_logo.png
--title="企业接口自动化测试报告"
--project=enterprise-api-auto
--environment=test
在用例中通过装饰器增强报告:
python复制import allure
@allure.feature("用户管理")
@allure.story("登录功能")
@allure.title("用户登录成功场景")
@allure.severity(allure.severity_level.CRITICAL)
@allure.link("https://confluence.example.com/display/API/User+Login", name="API文档")
@allure.issue("BUG-1234", "登录偶发失败问题")
@allure.description("""
### 测试场景
验证用户使用正确用户名密码能够成功登录系统
### 预期结果
1. 返回HTTP状态码200
2. 响应中包含有效token
3. 返回消息为"登录成功"
""")
def test_login_success(api_client):
# 测试实现...
这样生成的报告包含:
- 自定义logo和标题
- 测试用例与需求/缺陷系统的链接
- 丰富的描述和预期结果
- 按模块和优先级组织用例
- 环境信息展示
6.2 Jenkins流水线配置
完整的Jenkinsfile示例:
groovy复制pipeline {
agent {
label 'python-agent'
}
environment {
PYTHON_VERSION = '3.9'
PROJECT_DIR = 'enterprise_api_auto'
}
stages {
stage('准备环境') {
steps {
script {
// 检查Python版本
def pythonVersion = sh(
script: 'python --version',
returnStdout: true
).trim()
if (!pythonVersion.contains("Python ${PYTHON_VERSION}")) {
error("需要Python ${PYTHON_VERSION}, 当前是 ${pythonVersion}")
}
// 创建虚拟环境
sh """
python -m venv ${PROJECT_DIR}/venv
source ${PROJECT_DIR}/venv/bin/activate
pip install pip-tools
"""
}
}
}
stage('安装依赖') {
steps {
sh """
source ${PROJECT_DIR}/venv/bin/activate
cd ${PROJECT_DIR}
pip-sync requirements.txt
"""
}
}
stage('执行测试') {
steps {
sh """
source ${PROJECT_DIR}/venv/bin/activate
cd ${PROJECT_DIR}
pytest --env=${params.ENV} --alluredir=report/allure_raw
"""
}
}
stage('生成报告') {
steps {
sh """
source ${PROJECT_DIR}/venv/bin/activate
cd ${PROJECT_DIR}
allure generate report/allure_raw -o report/allure_html --clean
"""
allure([
includeProperties: false,
jdk: '',
properties: [],
reportBuildPolicy: 'ALWAYS',
results: [[path: 'report/allure_raw']]
])
}
}
}
post {
always {
// 清理工作空间
cleanWs()
// 发送通知
script {
def reportUrl = "${env.BUILD_URL}allure/"
def subject = "接口测试完成: ${currentBuild.result ?: 'SUCCESS'}"
def body = """
构建结果: ${currentBuild.result ?: 'SUCCESS'}
构建编号: ${env.BUILD_NUMBER}
报告地址: ${reportUrl}
"""
// 邮件通知
emailext(
subject: subject,
body: body,
to: 'qa-team@example.com',
attachLog: true
)
// 企业微信机器人通知
withCredentials([string(credentialsId: 'wechat-webhook', variable: 'WEBHOOK_URL')]) {
sh """
curl '${WEBHOOK_URL}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"content": "### ${subject}\\n> ${body.replace('\n', '\\n> ')}"
}
}'
"""
}
}
}
}
parameters {
choice(
name: 'ENV',
choices: ['dev', 'test', 'staging'],
description: '选择测试环境'
)
}
}
这个流水线实现了:
- 多环境支持
- 依赖精确控制
- 测试执行与报告生成
- 多通道通知
- 完善的错误处理
7. 企业级特性增强
7.1 测试数据隔离方案
在企业环境中,测试数据污染是常见问题。我们采用以下策略:
- 数据工厂模式:动态生成测试数据
python复制from faker import Faker
from datetime import datetime, timedelta
class UserDataFactory:
def __init__(self):
self.fake = Faker(locale='zh_CN')
def create_user_data(self, role: str = "normal"):
"""生成用户测试数据"""
base_name = self.fake.user_name()
return {
"username": f"auto_{base_name}_{datetime.now().strftime('%Y%m%d%H%M%S')}",
"password": "P@ssw0rd123",
"email": f"{base_name}@test.com",
"phone": self.fake.phone_number(),
"role": role,
"created_at": (datetime.now() - timedelta(days=1)).isoformat()
}
- 测试数据清理:通过API或DB操作清理
python复制import pymysql
class DBManager:
def __init__(self, env: str = "test"):
config = ConfigReader().get_env_config(env)
self.connection = pymysql.connect(
host=config["db_host"],
user=config["db_user"],
password=config["db_password"],
database=config["db_name"],
cursorclass=pymysql.cursors.DictCursor
)
def cleanup_test_users(self, prefix: str = "auto_"):
"""清理测试创建的用户"""
with self.connection.cursor() as cursor:
sql = "DELETE FROM users WHERE username LIKE %s"
cursor.execute(sql, (f"{prefix}%",))
self.connection.commit()
return cursor.rowcount
- 数据快照:在测试前备份关键数据
python复制import pickle
class DataSnapshot:
@staticmethod
def take_snapshot(api_client, snapshot_file: str):
"""获取当前系统数据快照"""
critical_data = {
"users": api_client.get("/api/v1/users").json(),
"orders": api_client.get("/api/v1/orders").json()
}
with open(snapshot_file, "wb") as f:
pickle.dump(critical_data, f)
@staticmethod
def restore_snapshot(api_client, snapshot_file: str):
"""恢复系统数据到快照状态"""
with open(snapshot_file, "rb") as f:
critical_data = pickle.load(f)
# 实现数据恢复逻辑...
7.2 接口加密处理
对于需要加密的接口,我们封装了加解密工具:
python复制from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
import hashlib
class CryptoUtils:
def __init__(self, key: str, iv: str = None):
self.key = hashlib.md5(key.encode()).digest()
self.iv = iv.encode() if iv else self.key
def aes_encrypt(self, data: str) -> str:
"""AES加密"""
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
padded_data = pad(data.encode(), AES.block_size)
encrypted = cipher.encrypt(padded_data)
return base64.b64encode(encrypted).decode()
def aes_decrypt(self, encrypted_data: str) -> str:
"""AES解密"""
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
encrypted = base64.b64decode(encrypted_data)
decrypted = cipher.decrypt(encrypted)
return unpad(decrypted, AES.block_size).decode()
@staticmethod
def md5_sign(params: dict, secret: str) -> str:
"""生成MD5签名"""
param_str = "&".join(
f"{k}={v}" for k, v in sorted(params.items())
)
sign_str = f"{param_str}&{secret}"
return hashlib.md5(sign_str.encode()).hexdigest()
在HTTP客户端中集成加密:
python复制class SecureHttpClient(HttpClient):
def __init__(self, env: str = "test"):
super().__init__(env)
self.crypto = CryptoUtils(
key=self.config["crypto_key"],
iv=self.config.get("crypto_iv")
)
def post_encrypted(self, api_path: str, data: dict, **kwargs):
"""发送加密的POST请求"""
# 1. 参数排序并签名
timestamp = int(time.time())
data["timestamp"] = timestamp
data["sign"] = CryptoUtils.md5_sign(data, self.config["api_secret"])
# 2. 加密请求体
encrypted_data = self.crypto.aes_encrypt(json.dumps(data))
encrypted_payload = {
"data": encrypted_data,
"version": "1.0"
}
# 3. 发送请求
return self.post(api_path, json=encrypted_payload, **kwargs)
7.3 性能监控集成
在框架中集成简单的性能监控:
python复制import time
from dataclasses import dataclass
from typing import List
import statistics
@dataclass
class PerformanceResult:
api_name: str
total_requests: int
avg_response_time: float
min_response_time: float
max_response_time: float
p95: float
success_rate: float
class PerformanceMonitor:
def __init__(self):
self.records = []
def record_request(self, api_name: str, duration: float, success: bool):
"""记录请求性能数据"""
self.records.append({
"api_name": api_name,
"timestamp": time.time(),
"duration": duration,
"success": success
})
def get_summary(self, api_name: str = None) -> PerformanceResult:
"""获取性能摘要"""
records = [
r for r in self.records
if api_name is None or r["api_name"] == api_name
]
if not records:
return None
durations = [r["duration"] for r in records]
success_count = sum(1 for r in records if r["success"])
return PerformanceResult(
api_name=api_name or "ALL",
total_requests=len(records),
avg_response_time=statistics.mean(durations),
min_response_time=min(durations),
max_response_time=max(durations),
p95=statistics.quantiles(durations, n=20)[-1],
success_rate=success_count / len(records)
)
def generate_report(self):
"""生成性能报告"""
# 实现报告生成逻辑...
在HTTP客户端中集成监控:
python复制class MonitoredHttpClient(HttpClient):
def __init__(self, env: str = "test"):
super().__init__(env)
self.monitor = PerformanceMonitor()
def _send_request(self, method: str, api_path: str, **kwargs):
start_time = time.time()
try:
response = super()._send_request(method, api_path, **kwargs)
self.monitor.record_request(
api_path,
time.time() - start_time,
response.status_code < 400
)
return response
except Exception as e:
self.monitor.record_request(
api_path,
time.time() - start_time,
False
)
raise
8. 框架演进与团队协作
8.1 版本控制策略
企业项目中推荐使用Git Flow工作流:
- 分支规范:
master:生产环境对应分支,每次提交都是可发布的版本develop