hyb
2025-12-23 c980682a1fe205d8c21d349e9fc6b9e4951aea34
造数脚本
75 files added
1 files deleted
2233221 ■■■■■ changed files
测试组/脚本/造数脚本 1 ●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/.idea/.gitignore 8 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/.idea/inspectionProfiles/profiles_settings.xml 6 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/.idea/misc.xml 10 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/.idea/modules.xml 8 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/.idea/vcs.xml 6 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/.idea/造数脚本.iml 10 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/TestData/userinfo.xlsx patch | view | raw | blame | history
测试组/脚本/造数脚本2/Util/__pycache__/dingtalk_helper.cpython-312.pyc patch | view | raw | blame | history
测试组/脚本/造数脚本2/Util/__pycache__/random_util.cpython-312.pyc patch | view | raw | blame | history
测试组/脚本/造数脚本2/Util/__pycache__/stress_test_report_generator.cpython-312.pyc patch | view | raw | blame | history
测试组/脚本/造数脚本2/Util/dingtalk_helper.py 87 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/Util/random_util.py 62 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/Util/stress_test_report_generator.py 456 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/main.py 68 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541.details.csv 2 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541.html 50 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541.summary.json 40 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541_latency_hist.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541_rps.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442.details.csv 309340 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442.html 246 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442.summary.json 396471 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442_latency_hist.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442_rps.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/华东师范大学二期/并发创建笼架.py 237 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/压测脚本使用教程.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508.details.csv 100001 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508.html 50 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508.summary.json 977447 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508_latency_hist.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508_rps.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/个体信息.py 281 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/临床检测.py 22089 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/动物检疫.py 15248 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/昆明理工大学/病毒检测.py 241 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544.details.csv 101 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544.html 50 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544.summary.json 963 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544_latency_hist.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544_rps.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/河北大学/设备维保通知.py 112 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/河北大学/设备维保通知_优化.py 166 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/河北大学/设备维保通知_压测.py 221 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917.details.csv 10001 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917.html 50 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917.summary.json 91983 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917_latency_hist.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917_rps.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052.details.csv 10001 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052.html 50 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052.summary.json 92095 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052_latency_hist.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052_rps.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050.details.csv 10001 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050.html 50 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050.summary.json 91971 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050_latency_hist.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050_rps.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009.details.csv 10001 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009.html 50 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009.summary.json 92095 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009_latency_hist.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009_rps.png patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/动物房管理新建动物房.py 240 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/清华/我的动物.py 153 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本2/齐鲁/TestData/userinfo.xlsx patch | view | raw | blame | history
测试组/脚本/造数脚本2/齐鲁/load_test_report/齐鲁医院pda更新笼位性能测试报告.docx patch | view | raw | blame | history
测试组/脚本/造数脚本2/齐鲁/pda更新笼位.py 402 ●●●●● patch | view | raw | blame | history
测试组/脚本/造数脚本
File was deleted
测试组/脚本/造数脚本2/.idea/.gitignore
New file
@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
测试组/脚本/造数脚本2/.idea/inspectionProfiles/profiles_settings.xml
New file
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
  <settings>
    <option name="USE_PROJECT_PROFILE" value="false" />
    <version value="1.0" />
  </settings>
</component>
测试组/脚本/造数脚本2/.idea/misc.xml
New file
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Black">
    <option name="sdkName" value="Python 3.12 (造数脚本)" />
  </component>
  <component name="ProjectRootManager" version="2" project-jdk-name="G:\Anaconda3" project-jdk-type="Python SDK" />
  <component name="PythonCompatibilityInspectionAdvertiser">
    <option name="version" value="3" />
  </component>
</project>
测试组/脚本/造数脚本2/.idea/modules.xml
New file
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/造数脚本.iml" filepath="$PROJECT_DIR$/.idea/造数脚本.iml" />
    </modules>
  </component>
</project>
测试组/脚本/造数脚本2/.idea/vcs.xml
New file
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="" vcs="Git" />
  </component>
</project>
测试组/脚本/造数脚本2/.idea/造数脚本.iml
New file
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
  <component name="NewModuleRootManager">
    <content url="file://$MODULE_DIR$">
      <excludeFolder url="file://$MODULE_DIR$/.venv" />
    </content>
    <orderEntry type="jdk" jdkName="G:\Anaconda3" jdkType="Python SDK" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>
测试组/脚本/造数脚本2/TestData/userinfo.xlsx
Binary files differ
测试组/脚本/造数脚本2/Util/__pycache__/dingtalk_helper.cpython-312.pyc
Binary files differ
测试组/脚本/造数脚本2/Util/__pycache__/random_util.cpython-312.pyc
Binary files differ
测试组/脚本/造数脚本2/Util/__pycache__/stress_test_report_generator.cpython-312.pyc
Binary files differ
测试组/脚本/造数脚本2/Util/dingtalk_helper.py
New file
@@ -0,0 +1,87 @@
import time
import hmac
import hashlib
import base64
import urllib.parse
import requests
import json
class DingTalkHelper:
    def __init__(self, access_token, secret):
        self.access_token = access_token
        self.secret = secret
    def calculate_sign(self):
        # 获取当前时间戳,单位为毫秒
        timestamp = str(round(time.time() * 1000))
        # 使用 HMAC SHA256 加密算法生成签名
        string_to_sign = '{}\n{}'.format(timestamp, self.secret)
        string_to_sign_enc = string_to_sign.encode('utf-8')
        secret_enc = self.secret.encode('utf-8')
        # 计算 HMAC 值
        hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
        # 使用 base64 编码并对 URL 进行编码
        sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
        return timestamp, sign
    def send_markdown(self, title, text, is_at_all=False):
        timestamp, sign = self.calculate_sign()
        dingtalk_webhook = f'https://oapi.dingtalk.com/robot/send?access_token={self.access_token}&timestamp={timestamp}&sign={sign}'
        payload = {
            "msgtype": "markdown",
            "markdown": {
                "title": title,
                "text": text
            },
            "at": {
                "isAtAll": is_at_all
            }
        }
        try:
            response = requests.post(dingtalk_webhook, json=payload)
            if response.status_code == 200:
                print("Markdown消息发送成功!")
            else:
                print(f"发送失败,状态码:{response.status_code}")
        except Exception as e:
            print(f"发送异常:{e}")
    def send_message(self, message):
        # 计算签名和时间戳
        timestamp, sign = self.calculate_sign()
        # 构建钉钉 Webhook URL,拼接上 timestamp 和 sign
        dingtalk_webhook = f'https://oapi.dingtalk.com/robot/send?access_token={self.access_token}&timestamp={timestamp}&sign={sign}'
        headers = {
            'Content-Type': 'application/json',
        }
        # 构建发送的消息内容
        payload = {
            "msgtype": "text",
            "text": {
                "content": message
            }
        }
        try:
            # 发送请求
            response = requests.post(dingtalk_webhook, json=payload, headers=headers)
            # 输出返回的状态码和响应内容
            print(f"Response status: {response.status_code}")
            print(f"Response text: {response.text}")
            if response.status_code == 200:
                print("Message sent successfully!")
            else:
                print("Failed to send message.")
        except Exception as e:
            print(f"Error occurred: {e}")
测试组/脚本/造数脚本2/Util/random_util.py
New file
@@ -0,0 +1,62 @@
import random
from datetime import datetime, timedelta
class RandomUtil:
    @staticmethod
    # 随机生成数字字符串
    def generate_random_number_string(start: str, end: str) -> str:
        """
        在指定范围内生成一个随机数字字符串。
        :param start: 范围起始值(包含),数字字符串形式
        :param end: 范围终止值(包含),数字字符串形式
        :return: 生成的随机数字字符串
        :raises ValueError: 如果输入的字符串不能转换为数字或者起始值大于终止值
        """
        try:
            start_num = int(start)
            end_num = int(end)
        except ValueError as e:
            raise ValueError("输入的字符串必须为有效的数字") from e
        if start_num > end_num:
            raise ValueError("起始值不能大于终止值")
        # 使用 random.randint 生成包含 start_num 和 end_num 的随机数
        random_number = random.randint(start_num, end_num)
        return str(random_number)
    # 随机生成年月日
    def generate_random_date(start_date: str, end_date: str) -> str:
        """
        在指定的年月日范围内随机生成一个日期字符串,格式为 %Y-%m-%d。
        :param start_date: 起始日期字符串,格式为 "YYYY-MM-DD"
        :param end_date: 结束日期字符串,格式为 "YYYY-MM-DD"
        :return: 随机生成的日期字符串
        :raises ValueError: 如果日期格式不正确或者起始日期大于结束日期
        """
        try:
            start = datetime.strptime(start_date, "%Y-%m-%d")
            end = datetime.strptime(end_date, "%Y-%m-%d")
        except ValueError as e:
            raise ValueError("输入的日期字符串必须为 'YYYY-MM-DD' 格式") from e
        if start > end:
            raise ValueError("起始日期不能大于结束日期")
        # 计算起始和结束日期之间的天数差
        delta_days = (end - start).days
        # 随机选择一个偏移天数
        random_days = random.randint(0, delta_days)
        # 计算随机日期
        random_date = start + timedelta(days=random_days)
        return random_date.strftime("%Y-%m-%d")
# 示例用法
if __name__ == '__main__':
    start = "1000"
    end = "9999"
    random_str = RandomUtil.generate_random_number_string(start, end)
    print("随机生成的数字字符串为:", random_str)
测试组/脚本/造数脚本2/Util/stress_test_report_generator.py
New file
@@ -0,0 +1,456 @@
"""
压测报告生成器(中文报告)
依赖:
  - numpy
  - pandas (可选,用于更方便的表格导出)
  - matplotlib (用于绘图)
  - python-docx (可选,用于生成 Word 文档)
使用场景:在你的压测脚本中,逐条调用 `recorder.record_result(...)` 或者在压测结束后把结果一次性传入 `bulk_record`,
然后调用 `generate_report(output_dir, formats=['html','docx','json','csv'])`。
输出:HTML 报告(含统计与图表)、可选的 Word 报告、JSON/CSV 明细文件和图像文件。
"""
from __future__ import annotations
import os
import json
import math
import time
import statistics
import datetime
from typing import List, Optional, Dict, Any
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False    # 正常显示负号
try:
    import numpy as np
except Exception:
    np = None
try:
    import pandas as pd
except Exception:
    pd = None
try:
    import matplotlib
    matplotlib.use('Agg')
    import matplotlib.pyplot as plt
except Exception:
    plt = None
try:
    from docx import Document
    from docx.shared import Inches
except Exception:
    Document = None
class RequestRecord:
    """单条请求记录数据结构。"""
    def __init__(self, index: int, timestamp: float, status_code: int, latency_ms: float,
                 response_size: Optional[int] = None, error: Optional[str] = None, extra: Optional[Dict] = None):
        self.index = index
        self.timestamp = timestamp  # unix 时间戳,秒
        self.status_code = status_code
        self.latency_ms = latency_ms
        self.response_size = response_size
        self.error = error
        self.extra = extra or {}
    def to_dict(self) -> Dict[str, Any]:
        return {
            'index': self.index,
            'timestamp': self.timestamp,
            'datetime': datetime.datetime.fromtimestamp(self.timestamp).isoformat(sep=' ', timespec='seconds'),
            'status_code': self.status_code,
            'latency_ms': self.latency_ms,
            'response_size': self.response_size,
            'error': self.error,
            **(self.extra or {})
        }
class LoadTestReportGenerator:
    """压测报告生成器。将请求结果记录并生成中文报告。"""
    def __init__(self, test_name: str = '压测任务', report_title: Optional[str] = None):
        self.test_name = test_name
        self.report_title = report_title or f"{test_name} 报告"
        self.records: List[RequestRecord] = []
        self._started_at: Optional[float] = None
        self._ended_at: Optional[float] = None
    # ---------- 记录方法 ----------
    def record_result(self, index: int, timestamp: float, status_code: int, latency_ms: float,
                      response_size: Optional[int] = None, error: Optional[str] = None, extra: Optional[Dict] = None):
        """记录单条请求结果。timestamp 为 unix 时间戳(秒)。"""
        rec = RequestRecord(index, timestamp, status_code, latency_ms, response_size, error, extra)
        self.records.append(rec)
        if self._started_at is None or timestamp < self._started_at:
            self._started_at = timestamp
        if self._ended_at is None or timestamp > self._ended_at:
            self._ended_at = timestamp
    def bulk_record(self, results: List[Dict[str, Any]]):
        """一次性批量导入结果。每一项是 dict,需包含 index,timestamp,status_code,latency_ms 等字段。"""
        for r in results:
            self.record_result(
                index=r.get('index', len(self.records) + 1),
                timestamp=r['timestamp'],
                status_code=int(r.get('status_code', 0)),
                latency_ms=float(r.get('latency_ms', 0)),
                response_size=r.get('response_size'),
                error=r.get('error'),
                extra=r.get('extra')
            )
    # ---------- 统计方法 ----------
    def compute_stats(self) -> Dict[str, Any]:
        if not self.records:
            return {}
        latencies = [r.latency_ms for r in self.records if r.latency_ms is not None]
        statuses = [r.status_code for r in self.records]
        errors = [r for r in self.records if r.error]
        total = len(self.records)
        success_count = sum(1 for s in statuses if 200 <= s < 300)
        fail_count = total - success_count
        duration = (self._ended_at - self._started_at) if (self._started_at and self._ended_at and self._ended_at > self._started_at) else None
        duration = float(duration) if duration else 0.0
        throughput = (total / duration) if duration > 0 else 0.0
        # 使用 numpy 计算分位数(如果可用),否则用纯 python
        if np:
            p50 = float(np.percentile(latencies, 50)) if latencies else 0
            p90 = float(np.percentile(latencies, 90)) if latencies else 0
            p95 = float(np.percentile(latencies, 95)) if latencies else 0
            p99 = float(np.percentile(latencies, 99)) if latencies else 0
        else:
            lat_sorted = sorted(latencies)
            def percentile(lst, q):
                if not lst:
                    return 0
                k = (len(lst)-1) * (q/100)
                f = math.floor(k)
                c = math.ceil(k)
                if f == c:
                    return lst[int(k)]
                d0 = lst[int(f)] * (c-k)
                d1 = lst[int(c)] * (k-f)
                return d0 + d1
            p50 = percentile(lat_sorted, 50)
            p90 = percentile(lat_sorted, 90)
            p95 = percentile(lat_sorted, 95)
            p99 = percentile(lat_sorted, 99)
        status_groups: Dict[int, int] = {}
        for s in statuses:
            status_groups[s] = status_groups.get(s, 0) + 1
        # 每秒请求数(RPS)时间序列
        rps_series = {}
        for r in self.records:
            sec = int(r.timestamp)
            rps_series[sec] = rps_series.get(sec, 0) + 1
        # 简单错误聚合
        error_summary: Dict[str, int] = {}
        for r in errors:
            key = r.error if r.error else f'status_{r.status_code}'
            error_summary[key] = error_summary.get(key, 0) + 1
        stats = {
            'total_requests': total,
            'success_count': success_count,
            'fail_count': fail_count,
            'success_rate': success_count / total if total else 0,
            'duration_seconds': duration,
            'throughput_rps': throughput,
            'latency_ms': {
                'min': min(latencies) if latencies else 0,
                'max': max(latencies) if latencies else 0,
                'avg': statistics.mean(latencies) if latencies else 0,
                'median': p50,
                'p90': p90,
                'p95': p95,
                'p99': p99,
            },
            'status_groups': status_groups,
            'error_summary': error_summary,
            'rps_series': sorted(list(rps_series.items())),  # [(sec, count), ...]
        }
        return stats
    # ---------- 报告输出 ----------
    def _ensure_dir(self, path: str):
        if not os.path.exists(path):
            os.makedirs(path, exist_ok=True)
    def generate_report(self, output_dir: str = './load_test_report', formats: Optional[List[str]] = None):
        """
        生成报告。
        formats: 列表,支持 'html','docx','json','csv'。
        返回生成的文件路径字典。
        """
        formats = formats or ['html', 'json']
        self._ensure_dir(output_dir)
        stats = self.compute_stats()
        timestamp_str = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        base_name = f"{self.test_name.replace(' ', '_')}_{timestamp_str}"
        outputs = {}
        # 1) 输出 JSON 统计与明细
        if 'json' in formats:
            json_path = os.path.join(output_dir, base_name + '.summary.json')
            with open(json_path, 'w', encoding='utf-8') as f:
                json.dump({'stats': stats, 'records': [r.to_dict() for r in self.records]}, f, ensure_ascii=False, indent=2)
            outputs['json'] = json_path
        # 2) 输出 CSV 明细(如果 pandas 可用则用 pandas,否则手写)
        if 'csv' in formats:
            csv_path = os.path.join(output_dir, base_name + '.details.csv')
            if pd:
                df = pd.DataFrame([r.to_dict() for r in self.records])
                df.to_csv(csv_path, index=False, encoding='utf-8-sig')
            else:
                # 手动写入
                import csv
                with open(csv_path, 'w', newline='', encoding='utf-8') as f:
                    writer = csv.DictWriter(f, fieldnames=list(self.records[0].to_dict().keys()))
                    writer.writeheader()
                    for r in self.records:
                        writer.writerow(r.to_dict())
            outputs['csv'] = csv_path
        # 3) 生成图表(延迟分布与RPS),保存为 PNG
        charts = {}
        if plt:
            try:
                # 延迟直方图
                latencies = [r.latency_ms for r in self.records if r.latency_ms is not None]
                if latencies:
                    plt.figure()
                    plt.hist(latencies, bins=50)
                    plt.title('响应时间分布 (ms)')
                    plt.xlabel('延迟 (ms)')
                    plt.ylabel('请求数量')
                    hist_path = os.path.join(output_dir, base_name + '_latency_hist.png')
                    plt.tight_layout()
                    plt.savefig(hist_path)
                    plt.close()
                    charts['latency_hist'] = hist_path
                # RPS 图
                times = [int(r.timestamp) for r in self.records]
                if times:
                    from collections import Counter
                    cnt = Counter(times)
                    xs = sorted(cnt.keys())
                    ys = [cnt[x] for x in xs]
                    plt.figure()
                    plt.plot(xs, ys)
                    plt.title('每秒请求数 (RPS)')
                    plt.xlabel('Unix 秒')
                    plt.ylabel('请求数')
                    rps_path = os.path.join(output_dir, base_name + '_rps.png')
                    plt.tight_layout()
                    plt.savefig(rps_path)
                    plt.close()
                    charts['rps'] = rps_path
            except Exception as e:
                print('生成图表时出错:', e)
        outputs['charts'] = charts
        # 4) 生成 HTML 报告
        if 'html' in formats:
            html_path = os.path.join(output_dir, base_name + '.html')
            html_content = self._build_html_report(stats, charts)
            with open(html_path, 'w', encoding='utf-8') as f:
                f.write(html_content)
            outputs['html'] = html_path
        # 5) 生成 Word 报告(可选)
        if 'docx' in formats and Document:
            try:
                docx_path = os.path.join(output_dir, base_name + '.docx')
                self._build_docx_report(stats, charts, docx_path)
                outputs['docx'] = docx_path
            except Exception as e:
                print('生成 docx 报告时出错:', e)
        return outputs
    def _build_html_report(self, stats: Dict[str, Any], charts: Dict[str, str]) -> str:
        # 这里构建一份中文的 HTML 模板(简洁风格)
        title = self.report_title
        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        summary_html = f"""
        <h2>摘要</h2>
        <ul>
          <li>报告名称:{title}</li>
          <li>生成时间:{now}</li>
          <li>总请求数:{stats.get('total_requests', 0)}</li>
          <li>成功数:{stats.get('success_count',0)},失败数:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}</li>
          <li>总耗时(秒):{stats.get('duration_seconds',0):.2f}</li>
          <li>平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}</li>
        </ul>
        """
        latency = stats.get('latency_ms', {})
        latency_html = f"""
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:{latency.get('min',0):.2f}</li>
          <li>最大:{latency.get('max',0):.2f}</li>
          <li>平均:{latency.get('avg',0):.2f}</li>
          <li>中位数(P50):{latency.get('median',0):.2f}</li>
          <li>P90:{latency.get('p90',0):.2f},P95:{latency.get('p95',0):.2f},P99:{latency.get('p99',0):.2f}</li>
        </ul>
        """
        status_html = '<h2>状态码分布</h2><ul>'
        for k, v in stats.get('status_groups', {}).items():
            status_html += f'<li>{k}: {v}</li>'
        status_html += '</ul>'
        error_html = '<h2>错误汇总</h2>'
        if stats.get('error_summary'):
            error_html += '<ul>'
            for k, v in stats.get('error_summary', {}).items():
                error_html += f'<li>{k}: {v}</li>'
            error_html += '</ul>'
        else:
            error_html += '<p>无错误记录</p>'
        charts_html = '<h2>图表</h2>'
        for name, path in charts.items():
            charts_html += f'<div><h3>{name}</h3><img src="{os.path.basename(path)}" alt="{name}" style="max-width:100%;height:auto;"/></div>'
        # 详细请求表(默认仅包含前 100 条,避免页面过大)
        details = [r.to_dict() for r in self.records[:100]]
        detail_rows = ''.join([f"<tr><td>{d['index']}</td><td>{d['datetime']}</td><td>{d['status_code']}</td><td>{d['latency_ms']}</td><td>{d['response_size']}</td><td>{d['error'] or ''}</td></tr>" for d in details])
        details_html = f"""
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          {detail_rows}
        </table>
        """
        html = f"""
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>{title}</title>
          <style>
            body{{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}}
            h2{{color:#2c3e50}}
            table{{border-collapse:collapse; width:100%}}
            th,td{{padding:6px; text-align:left}}
          </style>
        </head>
        <body>
          <h1>{title}</h1>
          {summary_html}
          {latency_html}
          {status_html}
          {error_html}
          {charts_html}
          {details_html}
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
        """
        # 若有图表,将图表文件拷贝到同目录(图表已保存在 output 目录),HTML 中用相对路径引用 basename
        return html
    def _build_docx_report(self, stats: Dict[str, Any], charts: Dict[str, str], docx_path: str):
        if not Document:
            raise RuntimeError('缺少 python-docx 库,无法生成 docx。')
        doc = Document()
        doc.add_heading(self.report_title, level=1)
        doc.add_paragraph(f"生成时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        doc.add_heading('摘要', level=2)
        doc.add_paragraph(f"总请求数:{stats.get('total_requests',0)}  成功:{stats.get('success_count',0)}  失败:{stats.get('fail_count',0)}  成功率:{stats.get('success_rate',0):.2%}")
        doc.add_paragraph(f"总耗时(秒):{stats.get('duration_seconds',0):.2f}  平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}")
        doc.add_heading('响应时间统计 (ms)', level=2)
        lat = stats.get('latency_ms', {})
        doc.add_paragraph(f"最小:{lat.get('min',0):.2f}  最大:{lat.get('max',0):.2f}  平均:{lat.get('avg',0):.2f}")
        doc.add_paragraph(f"P50:{lat.get('median',0):.2f}  P90:{lat.get('p90',0):.2f}  P95:{lat.get('p95',0):.2f}  P99:{lat.get('p99',0):.2f}")
        doc.add_heading('状态码分布', level=2)
        for k, v in stats.get('status_groups', {}).items():
            doc.add_paragraph(f"{k}: {v}")
        doc.add_heading('错误汇总', level=2)
        if stats.get('error_summary'):
            for k, v in stats.get('error_summary', {}).items():
                doc.add_paragraph(f"{k}: {v}")
        else:
            doc.add_paragraph('无错误记录')
        # 插入图表
        for name, path in charts.items():
            if os.path.exists(path):
                doc.add_heading(name, level=2)
                try:
                    doc.add_picture(path, width=Inches(6))
                except Exception:
                    doc.add_paragraph(f'无法插入图片:{path}')
        # 附加前 100 条请求明细
        doc.add_heading('请求明细(前100条)', level=2)
        table = doc.add_table(rows=1, cols=6)
        hdr_cells = table.rows[0].cells
        hdr_cells[0].text = '#'
        hdr_cells[1].text = '时间'
        hdr_cells[2].text = '状态码'
        hdr_cells[3].text = '延迟(ms)'
        hdr_cells[4].text = '响应大小'
        hdr_cells[5].text = '错误'
        for r in self.records[:100]:
            row_cells = table.add_row().cells
            d = r.to_dict()
            row_cells[0].text = str(d['index'])
            row_cells[1].text = d['datetime']
            row_cells[2].text = str(d['status_code'])
            row_cells[3].text = f"{d['latency_ms']}"
            row_cells[4].text = str(d.get('response_size', ''))
            row_cells[5].text = d.get('error') or ''
        doc.save(docx_path)
# ---------------- 使用示例 ----------------
if __name__ == '__main__':
    # 简单示例:模拟一些请求结果并生成报告
    gen = LoadTestReportGenerator(test_name='示例压测', report_title='示例压测详细报告')
    now = time.time()
    # 模拟 100 条请求
    import random
    for i in range(1, 101):
        ts = now + (i // 5)  # 每秒 5 个请求(模拟)
        lat = max(1.0, random.gauss(200, 50))
        status = 200 if random.random() > 0.05 else 500
        err = None if status == 200 else '500 Internal Server Error'
        gen.record_result(i, ts, status, lat, response_size=random.randint(500, 5000), error=err)
    outputs = gen.generate_report('./example_report', formats=['html', 'json', 'csv', 'docx'])
    print('已生成报告:', outputs)
测试组/脚本/造数脚本2/main.py
New file
@@ -0,0 +1,68 @@
import aiohttp
import asyncio
# 定义接口的URL
url = "http://ecnu.baoyizn.com:9992/zb/a/repertory/receive/receiveManage/save"
# 定义请求头
headers = {
    "Host": "ecnu.baoyizn.com:9992",
    "Connection": "keep-alive",
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "X-Requested-With": "XMLHttpRequest",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Origin": "http://ecnu.baoyizn.com:9992",
    "Referer": "http://ecnu.baoyizn.com:9992/zb/a/repertory/receive/receiveManage/form/add",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cookie": "jeeplus.session.id=21356cbeabb24cbfa12e9fc79590f9fe; rememberMe=/40LFf7oBYkAOgAMrLpNQlRYMxYPntgW0FcmV38n0bcNdiWBj5ODV9yDwIWIsKrYBjkOSB+9kStY/q1kq++cqu8VBevU9vQP4k4yGsRshf2f6kLx5Ql8eXFd7yfXrJd7bLEKDu9aL8aFvTpD3UR6TaoXDJfhhvyVQc8+wlq2lLC9ENONb9wfwdDrV4Ge3uNPnimIR/2U1krjvb1hVXu6ZblNPyWDURaaZoWEQ9Sqe+kB1fXpfOevuefPpg8ZfRZ+Jb56+62xBkIdg08H73aMc/d9njFtWNjl34hlWlS6cGwMLGVCGoAOktmrNv+cIUhYEty7vPh3DzfIp9b7h6Y2KkteGbrcjrGabvtZXZ5RyGY8TJtvF0vu3AFLVz9zaeocYHGjK3Bk55bPnTYXXY4ZHhpGdYKcPyobnS8f0tZxt8dYCOdnGDS7169WURj4VTUExWQ5rx9Bkn6VjuD1pEgogGq2SIs0iWrrXhvk+GjeS1ZbtL791zrV6piyWEN59Wj0XdW7h/nHdieY7CUKLWPcfAYK30pmrtghYovEiXNkk/3X9tYayr9yhSgYs0HchfXNbZAb/iRlNTEFY959XhsOnICE6SblY9i11jUXTC6gLeI3zAe4a8N53bk1GIP4f4OqZtt67LXxIpcbTsFKiOLzRHlAedfc076RnlAmPwIiNyYwCmD1ds1uMU+XGwp4zHgj2KOjS8Kp0HwhfejuCQsk/3PCkCWWspOqx48bIe0Jje+edf5UxJbpEgVDbrZ0Sbg8zw1qofD6Lbz3dCtcHS81lBUoAf6X3PL/0pQuJRpRlD/9SSIhv9uRPJeuvUcIQyC3gDlGiaQcgVMXvnBujMy5iLiKlBpmZcgFtIiiQEyVZ+I=; JSESSIONID=273B42D6BADDAC8D20B8F5A7FDB149AD; cna=wc8xH9vzsEcCAXgpk+X/ItGt; tfstk=fZ3omcgjD0rSmU1pEq4Wv8taYA-YJ8aa8pkLpJhe0-yb2Qi-pmqnpx1-wMhKmJDqn8RSp033-YH4U0hLpk8nBXwPv7lUuxDxKbULz0n3xxlzUQMKpxbnw4eKKXO7TJkEKJnJDFhSNyaeBwY9WbTBMzJrE87U3BP3s7JQzyLZNyaeHsQeXFcSIBTFCH4eiEPaayWzLaR005FU8arFziW4hWzULvrz0oPaOWPU4JyqK17UI2ujuLPp2cLnqdsUwu2Zil_h83E1BPlznw7e97qljbyc8wumcB_MZ8-1MJa8F2VodF_7zkmxPlgJ3aDnhxuzAqJvcva81loEMCsYFq0-uu0kIGao0xg0V4WyN7oSd24-JTbni22ub4rDRBaogWzmbjd1D8gmyqrordj_E4GEvomfEKeKXj3zmAJAovZnD4ZsyK7oQgkO0G5rTwN29qSCAuPbiRt9wk24NSAvTIAcf3ZzG7p9iIjCAuPbiRdDiG6Q4SNJB; HttpOnly=true; Secure=true; theme=blue; pageNo=1; pageSize=50"
}
# 定义请求体模板
data_template = {
    "id": "",
    "remainNum": "121",
    "researchGroup.id": "",
    "repertoryUnit": "21",
    "repertoryName": "测试",
    "receiveTime": "2024-08-13",
    "repertoryId": "f3ecf808422e421b8ca473458d6cf540",
    "repertory.name": "测试",
    "apply.id": "b2e8c68f27e243bc99a363bc2f8260d0",
    "apply.name": "管理员",
    "num": "1",
    "remarks": ""
}
# 定义异步发送请求的函数
async def send_request(session, data):
    async with session.post(url, headers=headers, data=data) as response:
        status_code = response.status
        response_text = await response.text()
        print(f"Status Code: {status_code}, Response: {response_text}")
# 定义主函数
async def main():
    # 定义并发请求的数量
    batch_size = 1000
    tasks = []
    async with aiohttp.ClientSession() as session:
        for i in range(batch_size):
            # 创建请求体
            data = data_template.copy()
            data["num"] = str(i + 1)
            # 创建并发请求任务
            tasks.append(send_request(session, data))
        # 执行所有请求任务
        await asyncio.gather(*tasks)
# 运行主函数
if __name__ == "__main__":
    asyncio.run(main())
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541.details.csv
New file
@@ -0,0 +1,2 @@
index,timestamp,datetime,status_code,latency_ms,response_size,error
1,1766135741.8356595,2025-12-19 17:15:41,200,33098.71220588684,8,
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541.docx
Binary files differ
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541.html
New file
@@ -0,0 +1,50 @@
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>压测详细报告</title>
          <style>
            body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
            h2{color:#2c3e50}
            table{border-collapse:collapse; width:100%}
            th,td{padding:6px; text-align:left}
          </style>
        </head>
        <body>
          <h1>压测详细报告</h1>
        <h2>摘要</h2>
        <ul>
          <li>报告名称:压测详细报告</li>
          <li>生成时间:2025-12-19 17:15:42</li>
          <li>总请求数:1</li>
          <li>成功数:1,失败数:0,成功率:100.00%</li>
          <li>总耗时(秒):0.00</li>
          <li>平均吞吐(req/s):0.00</li>
        </ul>
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:33098.71</li>
          <li>最大:33098.71</li>
          <li>平均:33098.71</li>
          <li>中位数(P50):33098.71</li>
          <li>P90:33098.71,P95:33098.71,P99:33098.71</li>
        </ul>
          <h2>状态码分布</h2><ul><li>200: 1</li></ul>
          <h2>错误汇总</h2><p>无错误记录</p>
          <h2>图表</h2><div><h3>latency_hist</h3><img src="压测任务_20251219_171541_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="压测任务_20251219_171541_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          <tr><td>1</td><td>2025-12-19 17:15:41</td><td>200</td><td>33098.71220588684</td><td>8</td><td></td></tr>
        </table>
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541.summary.json
New file
@@ -0,0 +1,40 @@
{
  "stats": {
    "total_requests": 1,
    "success_count": 1,
    "fail_count": 0,
    "success_rate": 1.0,
    "duration_seconds": 0.0,
    "throughput_rps": 0.0,
    "latency_ms": {
      "min": 33098.71220588684,
      "max": 33098.71220588684,
      "avg": 33098.71220588684,
      "median": 33098.71220588684,
      "p90": 33098.71220588684,
      "p95": 33098.71220588684,
      "p99": 33098.71220588684
    },
    "status_groups": {
      "200": 1
    },
    "error_summary": {},
    "rps_series": [
      [
        1766135741,
        1
      ]
    ]
  },
  "records": [
    {
      "index": 1,
      "timestamp": 1766135741.8356595,
      "datetime": "2025-12-19 17:15:41",
      "status_code": 200,
      "latency_ms": 33098.71220588684,
      "response_size": 8,
      "error": null
    }
  ]
}
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541_latency_hist.png
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251219_171541_rps.png
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442.details.csv
New file
Diff too large
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442.docx
Binary files differ
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442.html
New file
@@ -0,0 +1,246 @@
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>压测详细报告</title>
          <style>
            body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
            h2{color:#2c3e50}
            table{border-collapse:collapse; width:100%}
            th,td{padding:6px; text-align:left}
          </style>
        </head>
        <body>
          <h1>压测详细报告</h1>
        <h2>摘要</h2>
        <ul>
          <li>报告名称:压测详细报告</li>
          <li>生成时间:2025-12-20 15:04:46</li>
          <li>总请求数:40000</li>
          <li>成功数:98,失败数:39902,成功率:0.24%</li>
          <li>总耗时(秒):77576.53</li>
          <li>平均吞吐(req/s):0.52</li>
        </ul>
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:6501.80</li>
          <li>最大:60140.01</li>
          <li>平均:59948.44</li>
          <li>中位数(P50):60006.91</li>
          <li>P90:60055.25,P95:60062.55,P99:60068.00</li>
        </ul>
          <h2>状态码分布</h2><ul><li>200: 98</li><li>0: 39902</li></ul>
          <h2>错误汇总</h2><ul><li>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
: 38477</li><li>TimeoutError:: 1425</li></ul>
          <h2>图表</h2><div><h3>latency_hist</h3><img src="压测任务_20251220_150442_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="压测任务_20251220_150442_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          <tr><td>47</td><td>2025-12-19 17:31:45</td><td>200</td><td>6501.801252365112</td><td>8</td><td></td></tr><tr><td>35</td><td>2025-12-19 17:31:45</td><td>200</td><td>6536.803960800171</td><td>8</td><td></td></tr><tr><td>31</td><td>2025-12-19 17:31:45</td><td>200</td><td>6587.803363800049</td><td>8</td><td></td></tr><tr><td>80</td><td>2025-12-19 17:31:45</td><td>200</td><td>6601.800441741943</td><td>8</td><td></td></tr><tr><td>4</td><td>2025-12-19 17:31:46</td><td>200</td><td>6997.430086135864</td><td>8</td><td></td></tr><tr><td>21</td><td>2025-12-19 17:31:46</td><td>200</td><td>7473.469257354736</td><td>8</td><td></td></tr><tr><td>98</td><td>2025-12-19 17:31:47</td><td>200</td><td>8192.993879318237</td><td>8</td><td></td></tr><tr><td>37</td><td>2025-12-19 17:31:50</td><td>200</td><td>11399.858713150024</td><td>8</td><td></td></tr><tr><td>96</td><td>2025-12-19 17:31:50</td><td>200</td><td>11522.770166397095</td><td>8</td><td></td></tr><tr><td>19</td><td>2025-12-19 17:31:50</td><td>200</td><td>11687.269687652588</td><td>8</td><td></td></tr><tr><td>84</td><td>2025-12-19 17:31:50</td><td>200</td><td>11723.859310150146</td><td>8</td><td></td></tr><tr><td>85</td><td>2025-12-19 17:31:51</td><td>200</td><td>12288.209676742554</td><td>8</td><td></td></tr><tr><td>11</td><td>2025-12-19 17:31:52</td><td>200</td><td>13604.55298423767</td><td>8</td><td></td></tr><tr><td>43</td><td>2025-12-19 17:31:54</td><td>200</td><td>15098.32787513733</td><td>8</td><td></td></tr><tr><td>48</td><td>2025-12-19 17:31:55</td><td>200</td><td>16122.00403213501</td><td>8</td><td></td></tr><tr><td>5</td><td>2025-12-19 17:31:55</td><td>200</td><td>16604.77638244629</td><td>8</td><td></td></tr><tr><td>63</td><td>2025-12-19 17:31:55</td><td>200</td><td>16740.970611572266</td><td>8</td><td></td></tr><tr><td>87</td><td>2025-12-19 17:31:56</td><td>200</td><td>16841.984748840332</td><td>8</td><td></td></tr><tr><td>39</td><td>2025-12-19 17:31:56</td><td>200</td><td>17636.683702468872</td><td>8</td><td></td></tr><tr><td>71</td><td>2025-12-19 17:31:58</td><td>200</td><td>19641.02077484131</td><td>8</td><td></td></tr><tr><td>15</td><td>2025-12-19 17:32:00</td><td>200</td><td>20874.3417263031</td><td>8</td><td></td></tr><tr><td>88</td><td>2025-12-19 17:32:00</td><td>200</td><td>21562.708616256714</td><td>8</td><td></td></tr><tr><td>53</td><td>2025-12-19 17:32:01</td><td>200</td><td>21881.85214996338</td><td>8</td><td></td></tr><tr><td>8</td><td>2025-12-19 17:32:01</td><td>200</td><td>21970.46160697937</td><td>8</td><td></td></tr><tr><td>92</td><td>2025-12-19 17:32:01</td><td>200</td><td>21976.45878791809</td><td>8</td><td></td></tr><tr><td>90</td><td>2025-12-19 17:32:02</td><td>200</td><td>22839.781284332275</td><td>8</td><td></td></tr><tr><td>14</td><td>2025-12-19 17:32:04</td><td>200</td><td>25562.65163421631</td><td>8</td><td></td></tr><tr><td>6</td><td>2025-12-19 17:32:04</td><td>200</td><td>25733.7863445282</td><td>8</td><td></td></tr><tr><td>16</td><td>2025-12-19 17:32:05</td><td>200</td><td>26594.603061676025</td><td>8</td><td></td></tr><tr><td>42</td><td>2025-12-19 17:32:06</td><td>200</td><td>26934.176206588745</td><td>8</td><td></td></tr><tr><td>12</td><td>2025-12-19 17:32:06</td><td>200</td><td>27078.773975372314</td><td>8</td><td></td></tr><tr><td>95</td><td>2025-12-19 17:32:07</td><td>200</td><td>28102.483987808228</td><td>8</td><td></td></tr><tr><td>34</td><td>2025-12-19 17:32:07</td><td>200</td><td>28775.54178237915</td><td>8</td><td></td></tr><tr><td>76</td><td>2025-12-19 17:32:09</td><td>200</td><td>30250.354528427124</td><td>8</td><td></td></tr><tr><td>41</td><td>2025-12-19 17:32:10</td><td>200</td><td>31592.986345291138</td><td>8</td><td></td></tr><tr><td>46</td><td>2025-12-19 17:32:10</td><td>200</td><td>31739.563703536987</td><td>8</td><td></td></tr><tr><td>78</td><td>2025-12-19 17:32:11</td><td>200</td><td>31971.235513687134</td><td>8</td><td></td></tr><tr><td>1</td><td>2025-12-19 17:32:11</td><td>200</td><td>32221.757173538208</td><td>8</td><td></td></tr><tr><td>30</td><td>2025-12-19 17:32:12</td><td>200</td><td>33461.28559112549</td><td>8</td><td></td></tr><tr><td>89</td><td>2025-12-19 17:32:14</td><td>200</td><td>34931.60033226013</td><td>8</td><td></td></tr><tr><td>79</td><td>2025-12-19 17:32:14</td><td>200</td><td>35573.57048988342</td><td>8</td><td></td></tr><tr><td>49</td><td>2025-12-19 17:32:15</td><td>200</td><td>36576.865673065186</td><td>8</td><td></td></tr><tr><td>27</td><td>2025-12-19 17:32:16</td><td>200</td><td>37026.54409408569</td><td>8</td><td></td></tr><tr><td>75</td><td>2025-12-19 17:32:16</td><td>200</td><td>37259.377002716064</td><td>8</td><td></td></tr><tr><td>77</td><td>2025-12-19 17:32:17</td><td>200</td><td>37770.089626312256</td><td>8</td><td></td></tr><tr><td>7</td><td>2025-12-19 17:32:17</td><td>200</td><td>38723.75154495239</td><td>8</td><td></td></tr><tr><td>24</td><td>2025-12-19 17:32:18</td><td>200</td><td>39714.79916572571</td><td>8</td><td></td></tr><tr><td>45</td><td>2025-12-19 17:32:20</td><td>200</td><td>41640.74349403381</td><td>8</td><td></td></tr><tr><td>83</td><td>2025-12-19 17:32:21</td><td>200</td><td>42098.19722175598</td><td>8</td><td></td></tr><tr><td>69</td><td>2025-12-19 17:32:21</td><td>200</td><td>42372.363567352295</td><td>8</td><td></td></tr><tr><td>65</td><td>2025-12-19 17:32:21</td><td>200</td><td>42404.36124801636</td><td>8</td><td></td></tr><tr><td>66</td><td>2025-12-19 17:32:23</td><td>200</td><td>43801.53155326843</td><td>8</td><td></td></tr><tr><td>36</td><td>2025-12-19 17:32:23</td><td>200</td><td>43959.1498374939</td><td>8</td><td></td></tr><tr><td>61</td><td>2025-12-19 17:32:23</td><td>200</td><td>44357.62405395508</td><td>8</td><td></td></tr><tr><td>50</td><td>2025-12-19 17:32:25</td><td>200</td><td>46581.279039382935</td><td>8</td><td></td></tr><tr><td>86</td><td>2025-12-19 17:32:26</td><td>200</td><td>47098.071336746216</td><td>8</td><td></td></tr><tr><td>64</td><td>2025-12-19 17:32:26</td><td>200</td><td>47479.154109954834</td><td>8</td><td></td></tr><tr><td>10</td><td>2025-12-19 17:32:28</td><td>200</td><td>49046.358823776245</td><td>8</td><td></td></tr><tr><td>62</td><td>2025-12-19 17:32:28</td><td>200</td><td>49171.348571777344</td><td>8</td><td></td></tr><tr><td>17</td><td>2025-12-19 17:32:28</td><td>200</td><td>49231.353998184204</td><td>8</td><td></td></tr><tr><td>100</td><td>2025-12-19 17:32:29</td><td>200</td><td>49789.0567779541</td><td>8</td><td></td></tr><tr><td>93</td><td>2025-12-19 17:32:30</td><td>200</td><td>51503.05223464966</td><td>8</td><td></td></tr><tr><td>33</td><td>2025-12-19 17:32:31</td><td>200</td><td>52132.91811943054</td><td>8</td><td></td></tr><tr><td>29</td><td>2025-12-19 17:32:31</td><td>200</td><td>52545.90559005737</td><td>8</td><td></td></tr><tr><td>68</td><td>2025-12-19 17:32:32</td><td>200</td><td>53702.36945152283</td><td>8</td><td></td></tr><tr><td>58</td><td>2025-12-19 17:32:33</td><td>200</td><td>54482.28311538696</td><td>8</td><td></td></tr><tr><td>18</td><td>2025-12-19 17:32:35</td><td>200</td><td>55861.69981956482</td><td>8</td><td></td></tr><tr><td>44</td><td>2025-12-19 17:32:35</td><td>200</td><td>55958.28986167908</td><td>8</td><td></td></tr><tr><td>94</td><td>2025-12-19 17:32:35</td><td>200</td><td>56471.10819816589</td><td>8</td><td></td></tr><tr><td>51</td><td>2025-12-19 17:32:36</td><td>200</td><td>57140.769720077515</td><td>8</td><td></td></tr><tr><td>57</td><td>2025-12-19 17:32:36</td><td>200</td><td>57601.84645652771</td><td>8</td><td></td></tr><tr><td>91</td><td>2025-12-19 17:32:37</td><td>200</td><td>58333.40525627136</td><td>8</td><td></td></tr><tr><td>56</td><td>2025-12-19 17:32:38</td><td>200</td><td>59769.74272727966</td><td>8</td><td></td></tr><tr><td>73</td><td>2025-12-19 17:34:53</td><td>0</td><td>60012.06016540527</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>3</td><td>2025-12-19 17:34:53</td><td>0</td><td>60029.062271118164</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>54</td><td>2025-12-19 17:34:53</td><td>0</td><td>60038.05732727051</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>67</td><td>2025-12-19 17:34:53</td><td>0</td><td>60053.062200546265</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>13</td><td>2025-12-19 17:34:53</td><td>0</td><td>60050.058364868164</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>99</td><td>2025-12-19 17:34:53</td><td>0</td><td>60063.05909156799</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>82</td><td>2025-12-19 17:34:53</td><td>0</td><td>60063.05980682373</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>2</td><td>2025-12-19 17:34:53</td><td>0</td><td>60065.059185028076</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>81</td><td>2025-12-19 17:34:53</td><td>0</td><td>60067.060708999634</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>20</td><td>2025-12-19 17:34:53</td><td>0</td><td>60065.06037712097</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>22</td><td>2025-12-19 17:34:53</td><td>0</td><td>60066.06078147888</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>52</td><td>2025-12-19 17:34:53</td><td>0</td><td>60068.06182861328</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>97</td><td>2025-12-19 17:34:53</td><td>0</td><td>60069.05698776245</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>60</td><td>2025-12-19 17:34:53</td><td>0</td><td>60067.06118583679</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>9</td><td>2025-12-19 17:34:53</td><td>0</td><td>60012.06111907959</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>70</td><td>2025-12-19 17:34:53</td><td>0</td><td>60008.060932159424</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>23</td><td>2025-12-19 17:34:53</td><td>0</td><td>60012.06111907959</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>28</td><td>2025-12-19 17:34:53</td><td>0</td><td>60007.062673568726</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>25</td><td>2025-12-19 17:34:53</td><td>0</td><td>60031.68296813965</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>40</td><td>2025-12-19 17:34:53</td><td>0</td><td>60049.68023300171</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>59</td><td>2025-12-19 17:34:53</td><td>0</td><td>60065.68241119385</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>55</td><td>2025-12-19 17:34:53</td><td>0</td><td>60065.680742263794</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>26</td><td>2025-12-19 17:34:53</td><td>0</td><td>60068.69339942932</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>74</td><td>2025-12-19 17:34:53</td><td>0</td><td>60069.206953048706</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>38</td><td>2025-12-19 17:34:53</td><td>0</td><td>60067.683935165405</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>72</td><td>2025-12-19 17:34:53</td><td>0</td><td>60063.68398666382</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr><tr><td>32</td><td>2025-12-19 17:34:53</td><td>0</td><td>60062.68334388733</td><td>None</td><td>status_404:<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.26.2</center>
</body>
</html>
</td></tr>
        </table>
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442.summary.json
New file
Diff too large
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442_latency_hist.png
测试组/脚本/造数脚本2/华东师范大学二期/load_test_report/压测任务_20251220_150442_rps.png
测试组/脚本/造数脚本2/华东师范大学二期/并发创建笼架.py
New file
@@ -0,0 +1,237 @@
"""
集成压测脚本(带压测报告生成并通过钉钉发送摘要)
说明:
 - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。
 - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。
 - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。
 - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选)
 - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。
"""
import sys
import os
# 将上一级目录加入模块搜索路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio
import aiohttp
import time
import traceback
import datetime
from tqdm import tqdm
from Util.random_util import RandomUtil
from Util.dingtalk_helper import DingTalkHelper
# --- 配置 ---
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
apiname = "创建动物房笼架"
url = "http://192.168.6.190:5561/api/base/room/shelf/save"
headers = {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjYyMTk5MzIsInVzZXJuYW1lIjoiZ2x5In0.fN-yJ0SVrEd_GyIvPmaBL2nx1M3a1jloSOsXyu3E5_w",
    "Content-Type": "application/json"
}
NUM_WORKERS = 100
TOTAL_REQUESTS = 40000
MAX_RETRIES = 3
REQUEST_TIMEOUT = 60
OUTPUT_DIR = './load_test_report'
# --- 初始化 ---
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
LARGE_CONTENT = "压测中" * 500
FILES_PATH = "/userfiles/1588133301094375425/程序附件/notify/notify/2025/10/15/173933/cs.jpg"
def create_animal_data(idx: int):
    random_code = RandomUtil.generate_random_number_string(0, 999999999)
    random_data = RandomUtil.generate_random_date("2023-01-01", "2025-12-19")
    return {
            "id": "",
            "room": {
                "id": "2001936175429365762"
            },
            "facility": {
                "id": ""
            },
            "name": f"hyb压测笼架{random_code}",
            "code": f"hyb压测笼架{random_code}",
            "status": "1",
            "internalPrice": "",
            "externalPrice": "",
            "negotiatedPrice": "",
            "row": 10,
            "col": 10,
            "leftRight": "5",
            "topBottom": "5",
            "remarks": "",
            "priceConfig": {
                "id": "1860888070482100226"
            },
            "variety": {},
            "shelfCleanDate":  random_data,
            "otherCleanDate":  random_data
            }
async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES):
    attempt = 0
    last_err = None
    while attempt < max_retries:
        data = create_animal_data(index)
        start = time.time()
        try:
            async with session.post(url, json=data, headers=headers) as resp:
                text = await resp.text()
                latency_ms = (time.time() - start) * 1000.0
                status = resp.status
                if status == 200:
                    return {
                        'index': index,
                        'timestamp': time.time(),
                        'status_code': status,
                        'latency_ms': latency_ms,
                        'response_size': len(text) if text is not None else None,
                        'error': None
                    }
                else:
                    last_err = f'status_{status}:{text}'
                    attempt += 1
                    await asyncio.sleep(min(10, 2 ** attempt))
        except Exception as e:
            latency_ms = (time.time() - start) * 1000.0
            last_err = f'{type(e).__name__}:{str(e)}'
            attempt += 1
            await asyncio.sleep(min(10, 2 ** attempt))
    # 最终失败
    return {
        'index': index,
        'timestamp': time.time(),
        'status_code': 0,
        'latency_ms': latency_ms if 'latency_ms' in locals() else 0,
        'response_size': None,
        'error': last_err
    }
async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock):
    while True:
        idx = await queue.get()
        if idx is None:
            queue.task_done()
            break
        try:
            res = await perform_request(session, idx)
            # 记录到报告生成器
            gen.record_result(
                index=res['index'],
                timestamp=res['timestamp'],
                status_code=int(res['status_code']),
                latency_ms=float(res['latency_ms']),
                response_size=res.get('response_size'),
                error=res.get('error')
            )
            async with lock:
                if res['status_code'] and 200 <= res['status_code'] < 300:
                    success_counter['count'] += 1
                else:
                    failed_list.append((res['index'], res.get('error')))
                pbar.update(1)
        except Exception as e:
            async with lock:
                failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}'))
                pbar.update(1)
        finally:
            queue.task_done()
async def batch_create_animals(total: int, num_workers: int):
    # 动态加载报告生成器模块(支持中文文件名)
    gen = None
    try:
        import importlib.util
        script_dir = os.path.dirname(os.path.abspath(__file__))
        report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py')
        if os.path.exists(report_path):
            spec = importlib.util.spec_from_file_location('report_module', report_path)
            report_module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(report_module)
            LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator')
        else:
            # 备用:尝试直接导入模块名(若你的文件名已改为 ascii)
            from report_generator import LoadTestReportGenerator  # type: ignore
        gen = LoadTestReportGenerator(test_name='压测任务', report_title='压测详细报告')
    except Exception as e:
        print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e)
        raise
    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
    connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False)
    async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
        queue = asyncio.Queue()
        for i in range(1, total + 1):
            await queue.put(i)
        for _ in range(num_workers):
            await queue.put(None)
        success_counter = {'count': 0}
        failed_list = []
        lock = asyncio.Lock()
        with tqdm(total=total, desc='创建进度') as pbar:
            workers = [
                asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock))
                for i in range(num_workers)
            ]
            await asyncio.gather(*workers)
        # 任务完成,生成报告
        os.makedirs(OUTPUT_DIR, exist_ok=True)
        outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx'])
        stats = gen.compute_stats()
        # 构造钉钉摘要消息(中文)
        now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}']
        msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}")
        msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}")
        lat = stats.get('latency_ms', {})
        msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}")
        # 列出生成的报告文件
        file_list = []
        for k, v in outputs.items():
            if k == 'charts':
                for cname, cpath in v.items():
                    file_list.append(os.path.abspath(cpath))
            else:
                file_list.append(os.path.abspath(v))
        msg.append('生成文件:')
        for p in file_list:
            msg.append(p)
        final_msg = '\n'.join(msg)
        # 发送钉钉消息
        try:
            dingtalk_helper.send_message(final_msg)
        except Exception as e:
            print('发送钉钉消息失败:', e)
        print('\n[SUMMARY] 已生成报告并发送钉钉摘要。')
        print('成功数:', success_counter['count'], ' 失败数:', len(failed_list))
        if failed_list:
            print('失败示例(最多显示50条):')
            for idx, err in failed_list[:50]:
                print(f'  #{idx} => {err}')
if __name__ == '__main__':
    # 运行前建议先用小规模测试
    TOTAL_REQUESTS = TOTAL_REQUESTS
    NUM_WORKERS = NUM_WORKERS
    asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS))
测试组/脚本/造数脚本2/压测脚本使用教程.docx
Binary files differ
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508.details.csv
New file
Diff too large
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508.docx
Binary files differ
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508.html
New file
@@ -0,0 +1,50 @@
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>压测详细报告</title>
          <style>
            body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
            h2{color:#2c3e50}
            table{border-collapse:collapse; width:100%}
            th,td{padding:6px; text-align:left}
          </style>
        </head>
        <body>
          <h1>压测详细报告</h1>
        <h2>摘要</h2>
        <ul>
          <li>报告名称:压测详细报告</li>
          <li>生成时间:2025-10-16 23:35:15</li>
          <li>总请求数:100000</li>
          <li>成功数:100000,失败数:0,成功率:100.00%</li>
          <li>总耗时(秒):20485.64</li>
          <li>平均吞吐(req/s):4.88</li>
        </ul>
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:6943.04</li>
          <li>最大:59075.08</li>
          <li>平均:20493.84</li>
          <li>中位数(P50):20024.88</li>
          <li>P90:26928.12,P95:28865.69,P99:32812.51</li>
        </ul>
          <h2>状态码分布</h2><ul><li>200: 100000</li></ul>
          <h2>错误汇总</h2><p>无错误记录</p>
          <h2>图表</h2><div><h3>latency_hist</h3><img src="个体信息压测任务_20251016_233508_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="个体信息压测任务_20251016_233508_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          <tr><td>1</td><td>2025-10-16 17:53:42</td><td>200</td><td>11861.668586730957</td><td>4</td><td></td></tr><tr><td>36</td><td>2025-10-16 17:53:42</td><td>200</td><td>11966.110467910767</td><td>4</td><td></td></tr><tr><td>46</td><td>2025-10-16 17:53:42</td><td>200</td><td>12062.110424041748</td><td>4</td><td></td></tr><tr><td>49</td><td>2025-10-16 17:53:42</td><td>200</td><td>12132.709980010986</td><td>4</td><td></td></tr><tr><td>24</td><td>2025-10-16 17:53:42</td><td>200</td><td>12169.715166091919</td><td>4</td><td></td></tr><tr><td>15</td><td>2025-10-16 17:53:42</td><td>200</td><td>12181.530475616455</td><td>4</td><td></td></tr><tr><td>4</td><td>2025-10-16 17:53:42</td><td>200</td><td>12190.528869628906</td><td>4</td><td></td></tr><tr><td>9</td><td>2025-10-16 17:53:42</td><td>200</td><td>12194.527864456177</td><td>4</td><td></td></tr><tr><td>38</td><td>2025-10-16 17:53:42</td><td>200</td><td>12184.709787368774</td><td>4</td><td></td></tr><tr><td>11</td><td>2025-10-16 17:53:42</td><td>200</td><td>12212.530851364136</td><td>4</td><td></td></tr><tr><td>25</td><td>2025-10-16 17:53:42</td><td>200</td><td>12218.717575073242</td><td>4</td><td></td></tr><tr><td>22</td><td>2025-10-16 17:53:42</td><td>200</td><td>12237.71619796753</td><td>4</td><td></td></tr><tr><td>29</td><td>2025-10-16 17:53:42</td><td>200</td><td>12231.717109680176</td><td>4</td><td></td></tr><tr><td>8</td><td>2025-10-16 17:53:42</td><td>200</td><td>12252.525568008423</td><td>4</td><td></td></tr><tr><td>53</td><td>2025-10-16 17:53:42</td><td>200</td><td>12225.710391998291</td><td>4</td><td></td></tr><tr><td>2</td><td>2025-10-16 17:53:42</td><td>200</td><td>12263.529062271118</td><td>4</td><td></td></tr><tr><td>13</td><td>2025-10-16 17:53:42</td><td>200</td><td>12257.530450820923</td><td>4</td><td></td></tr><tr><td>6</td><td>2025-10-16 17:53:42</td><td>200</td><td>12269.530534744263</td><td>4</td><td></td></tr><tr><td>5</td><td>2025-10-16 17:53:42</td><td>200</td><td>12271.530151367188</td><td>4</td><td></td></tr><tr><td>44</td><td>2025-10-16 17:53:42</td><td>200</td><td>12255.710124969482</td><td>4</td><td></td></tr><tr><td>100</td><td>2025-10-16 17:53:42</td><td>200</td><td>12219.7105884552</td><td>4</td><td></td></tr><tr><td>42</td><td>2025-10-16 17:53:42</td><td>200</td><td>12263.710498809814</td><td>4</td><td></td></tr><tr><td>30</td><td>2025-10-16 17:53:42</td><td>200</td><td>12285.7186794281</td><td>4</td><td></td></tr><tr><td>45</td><td>2025-10-16 17:53:42</td><td>200</td><td>12279.711246490479</td><td>4</td><td></td></tr><tr><td>39</td><td>2025-10-16 17:53:42</td><td>200</td><td>12284.71040725708</td><td>4</td><td></td></tr><tr><td>33</td><td>2025-10-16 17:53:42</td><td>200</td><td>12290.711879730225</td><td>4</td><td></td></tr><tr><td>26</td><td>2025-10-16 17:53:42</td><td>200</td><td>12311.718225479126</td><td>4</td><td></td></tr><tr><td>70</td><td>2025-10-16 17:53:42</td><td>200</td><td>12281.710863113403</td><td>4</td><td></td></tr><tr><td>72</td><td>2025-10-16 17:53:42</td><td>200</td><td>12282.710552215576</td><td>4</td><td></td></tr><tr><td>32</td><td>2025-10-16 17:53:42</td><td>200</td><td>12311.718225479126</td><td>4</td><td></td></tr><tr><td>81</td><td>2025-10-16 17:53:42</td><td>200</td><td>12280.710697174072</td><td>4</td><td></td></tr><tr><td>43</td><td>2025-10-16 17:53:42</td><td>200</td><td>12316.7085647583</td><td>4</td><td></td></tr><tr><td>31</td><td>2025-10-16 17:53:42</td><td>200</td><td>12328.720092773438</td><td>4</td><td></td></tr><tr><td>61</td><td>2025-10-16 17:53:42</td><td>200</td><td>12313.71021270752</td><td>4</td><td></td></tr><tr><td>12</td><td>2025-10-16 17:53:42</td><td>200</td><td>12350.529909133911</td><td>4</td><td></td></tr><tr><td>34</td><td>2025-10-16 17:53:42</td><td>200</td><td>12343.712329864502</td><td>4</td><td></td></tr><tr><td>16</td><td>2025-10-16 17:53:42</td><td>200</td><td>12361.530542373657</td><td>4</td><td></td></tr><tr><td>85</td><td>2025-10-16 17:53:42</td><td>200</td><td>12318.710327148438</td><td>4</td><td></td></tr><tr><td>41</td><td>2025-10-16 17:53:42</td><td>200</td><td>12351.709842681885</td><td>4</td><td></td></tr><tr><td>59</td><td>2025-10-16 17:53:42</td><td>200</td><td>12344.709634780884</td><td>4</td><td></td></tr><tr><td>35</td><td>2025-10-16 17:53:42</td><td>200</td><td>12367.71035194397</td><td>4</td><td></td></tr><tr><td>98</td><td>2025-10-16 17:53:42</td><td>200</td><td>12328.714370727539</td><td>4</td><td></td></tr><tr><td>96</td><td>2025-10-16 17:53:42</td><td>200</td><td>12338.710069656372</td><td>4</td><td></td></tr><tr><td>47</td><td>2025-10-16 17:53:42</td><td>200</td><td>12372.710943222046</td><td>4</td><td></td></tr><tr><td>57</td><td>2025-10-16 17:53:42</td><td>200</td><td>12365.710496902466</td><td>4</td><td></td></tr><tr><td>86</td><td>2025-10-16 17:53:42</td><td>200</td><td>12354.711294174194</td><td>4</td><td></td></tr><tr><td>78</td><td>2025-10-16 17:53:42</td><td>200</td><td>12365.710258483887</td><td>4</td><td></td></tr><tr><td>55</td><td>2025-10-16 17:53:42</td><td>200</td><td>12382.710695266724</td><td>4</td><td></td></tr><tr><td>95</td><td>2025-10-16 17:53:42</td><td>200</td><td>12361.716270446777</td><td>4</td><td></td></tr><tr><td>19</td><td>2025-10-16 17:53:42</td><td>200</td><td>12427.526950836182</td><td>4</td><td></td></tr><tr><td>40</td><td>2025-10-16 17:53:42</td><td>200</td><td>12411.710739135742</td><td>4</td><td></td></tr><tr><td>10</td><td>2025-10-16 17:53:42</td><td>200</td><td>12435.532331466675</td><td>4</td><td></td></tr><tr><td>97</td><td>2025-10-16 17:53:42</td><td>200</td><td>12391.710758209229</td><td>4</td><td></td></tr><tr><td>94</td><td>2025-10-16 17:53:42</td><td>200</td><td>12395.710706710815</td><td>4</td><td></td></tr><tr><td>93</td><td>2025-10-16 17:53:43</td><td>200</td><td>12413.711786270142</td><td>4</td><td></td></tr><tr><td>56</td><td>2025-10-16 17:53:43</td><td>200</td><td>12442.712783813477</td><td>4</td><td></td></tr><tr><td>60</td><td>2025-10-16 17:53:43</td><td>200</td><td>12457.717657089233</td><td>4</td><td></td></tr><tr><td>51</td><td>2025-10-16 17:53:43</td><td>200</td><td>12465.712547302246</td><td>4</td><td></td></tr><tr><td>54</td><td>2025-10-16 17:53:43</td><td>200</td><td>12464.71357345581</td><td>4</td><td></td></tr><tr><td>23</td><td>2025-10-16 17:53:43</td><td>200</td><td>12505.717515945435</td><td>4</td><td></td></tr><tr><td>71</td><td>2025-10-16 17:53:43</td><td>200</td><td>12472.711324691772</td><td>4</td><td></td></tr><tr><td>82</td><td>2025-10-16 17:53:43</td><td>200</td><td>12466.711282730103</td><td>4</td><td></td></tr><tr><td>80</td><td>2025-10-16 17:53:43</td><td>200</td><td>12479.711771011353</td><td>4</td><td></td></tr><tr><td>87</td><td>2025-10-16 17:53:43</td><td>200</td><td>12482.711553573608</td><td>4</td><td></td></tr><tr><td>99</td><td>2025-10-16 17:53:43</td><td>200</td><td>12477.71143913269</td><td>4</td><td></td></tr><tr><td>69</td><td>2025-10-16 17:53:43</td><td>200</td><td>12505.709886550903</td><td>4</td><td></td></tr><tr><td>58</td><td>2025-10-16 17:53:43</td><td>200</td><td>12523.711204528809</td><td>4</td><td></td></tr><tr><td>84</td><td>2025-10-16 17:53:43</td><td>200</td><td>12509.711503982544</td><td>4</td><td></td></tr><tr><td>91</td><td>2025-10-16 17:53:43</td><td>200</td><td>12524.71137046814</td><td>4</td><td></td></tr><tr><td>67</td><td>2025-10-16 17:53:43</td><td>200</td><td>12543.71166229248</td><td>4</td><td></td></tr><tr><td>73</td><td>2025-10-16 17:53:43</td><td>200</td><td>12558.297157287598</td><td>4</td><td></td></tr><tr><td>3</td><td>2025-10-16 17:53:43</td><td>200</td><td>12613.115549087524</td><td>4</td><td></td></tr><tr><td>7</td><td>2025-10-16 17:53:43</td><td>200</td><td>12627.113103866577</td><td>4</td><td></td></tr><tr><td>65</td><td>2025-10-16 17:53:43</td><td>200</td><td>12596.299171447754</td><td>4</td><td></td></tr><tr><td>76</td><td>2025-10-16 17:53:43</td><td>200</td><td>12598.296880722046</td><td>4</td><td></td></tr><tr><td>48</td><td>2025-10-16 17:53:43</td><td>200</td><td>12659.295320510864</td><td>4</td><td></td></tr><tr><td>63</td><td>2025-10-16 17:53:43</td><td>200</td><td>12650.29788017273</td><td>4</td><td></td></tr><tr><td>79</td><td>2025-10-16 17:53:43</td><td>200</td><td>12640.297889709473</td><td>4</td><td></td></tr><tr><td>28</td><td>2025-10-16 17:53:43</td><td>200</td><td>12696.300983428955</td><td>4</td><td></td></tr><tr><td>62</td><td>2025-10-16 17:53:43</td><td>200</td><td>12693.297624588013</td><td>4</td><td></td></tr><tr><td>88</td><td>2025-10-16 17:53:43</td><td>200</td><td>12678.295850753784</td><td>4</td><td></td></tr><tr><td>27</td><td>2025-10-16 17:53:43</td><td>200</td><td>12736.303329467773</td><td>4</td><td></td></tr><tr><td>21</td><td>2025-10-16 17:53:43</td><td>200</td><td>12751.317262649536</td><td>4</td><td></td></tr><tr><td>89</td><td>2025-10-16 17:53:43</td><td>200</td><td>12715.296506881714</td><td>4</td><td></td></tr><tr><td>66</td><td>2025-10-16 17:53:43</td><td>200</td><td>12741.296768188477</td><td>4</td><td></td></tr><tr><td>52</td><td>2025-10-16 17:53:43</td><td>200</td><td>12762.2971534729</td><td>4</td><td></td></tr><tr><td>14</td><td>2025-10-16 17:53:43</td><td>200</td><td>12801.117181777954</td><td>4</td><td></td></tr><tr><td>75</td><td>2025-10-16 17:53:43</td><td>200</td><td>12768.296718597412</td><td>4</td><td></td></tr><tr><td>92</td><td>2025-10-16 17:53:43</td><td>200</td><td>12776.912212371826</td><td>4</td><td></td></tr><tr><td>77</td><td>2025-10-16 17:53:43</td><td>200</td><td>12795.911073684692</td><td>4</td><td></td></tr><tr><td>17</td><td>2025-10-16 17:53:43</td><td>200</td><td>12852.729082107544</td><td>4</td><td></td></tr><tr><td>90</td><td>2025-10-16 17:53:43</td><td>200</td><td>12802.912473678589</td><td>4</td><td></td></tr><tr><td>64</td><td>2025-10-16 17:53:43</td><td>200</td><td>13238.07168006897</td><td>4</td><td></td></tr><tr><td>68</td><td>2025-10-16 17:53:46</td><td>200</td><td>15427.53529548645</td><td>4</td><td></td></tr><tr><td>50</td><td>2025-10-16 17:53:46</td><td>200</td><td>16027.122259140015</td><td>4</td><td></td></tr><tr><td>37</td><td>2025-10-16 17:53:47</td><td>200</td><td>17389.598608016968</td><td>4</td><td></td></tr><tr><td>20</td><td>2025-10-16 17:53:48</td><td>200</td><td>17629.64653968811</td><td>4</td><td></td></tr><tr><td>18</td><td>2025-10-16 17:53:48</td><td>200</td><td>17783.334016799927</td><td>4</td><td></td></tr><tr><td>74</td><td>2025-10-16 17:53:50</td><td>200</td><td>20080.499410629272</td><td>4</td><td></td></tr><tr><td>83</td><td>2025-10-16 17:53:50</td><td>200</td><td>20292.65594482422</td><td>4</td><td></td></tr>
        </table>
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508.summary.json
New file
Diff too large
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508_latency_hist.png
测试组/脚本/造数脚本2/昆明理工大学/load_test_report/个体信息压测任务_20251016_233508_rps.png
测试组/脚本/造数脚本2/昆明理工大学/个体信息.py
New file
@@ -0,0 +1,281 @@
"""
集成压测脚本(带压测报告生成并通过钉钉发送摘要)
说明:
 - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。
 - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。
 - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。
 - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选)
 - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。
"""
import sys
import os
# 将上一级目录加入模块搜索路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio
import aiohttp
import time
import traceback
import datetime
from tqdm import tqdm
from Util.random_util import RandomUtil
from Util.dingtalk_helper import DingTalkHelper
# --- 配置 ---
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
apiname = "个体信息"
url = "http://192.168.6.168:5534/api/individual/individualrecord/individualRecord/save"
headers = {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2Nzk1MDksInVzZXJuYW1lIjoiZ2x5In0.aApZ-cXC_pIw6gdPZHg3TlsgZIaCPRrmlMDM3-zEhtg",
    "Content-Type": "application/json"
}
NUM_WORKERS = 10
TOTAL_REQUESTS = 100
MAX_RETRIES = 3
REQUEST_TIMEOUT = 60
OUTPUT_DIR = './load_test_report'
# --- 初始化 ---
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
LARGE_CONTENT = "备注压测中" * 10
FILES_PATH = "/userfiles/1463828311460319233/程序附件//baoyi/individual/individualrecord/2025/10/cs.jpg"
def create_animal_data(idx: int):
    random_code = RandomUtil.generate_random_number_string(0, 10000)
    random_code_grade = RandomUtil.generate_random_number_string(1, 2)
    random_code_sex = RandomUtil.generate_random_number_string(1, 2)
    random_date = RandomUtil.generate_random_date("2023-01-01", "2025-10-16")
    return {
        "individualExperimentRecordDTOList": [
            {
                "fileUrl": "",
                "id": "",
                "individualId": "",
                "experimentDate": random_date,
                "name": "3",
                "otherName": "",
                "remarks": "",
                "ultrasoundScanDate": "",
                "pregnancyStatus": "",
                "otherSituation": ""
            },
            {
                "fileUrl": "",
                "id": "",
                "individualId": "",
                "experimentDate": random_date,
                "name": "5",
                "otherName": "",
                "remarks": "",
                "ultrasoundScanDate": "",
                "pregnancyStatus": "",
                "otherSituation": ""
            }
        ],
        "id": "",
        "rfid": "random_code",
        "code": f"hyb压测模拟万人新建动物信息{random_code}",
        "variety": "食蟹猴",
        "birthDate": f"{random_date} 00:00:00",
        "deathDate": "",
        "grade": random_code_grade,
        "gender": random_code_sex,
        "father": {"id": ""},
        "mother": {"id": ""},
        "birthPlace": "",
        "room": {"id": "", "name": ""},
        "cage": {"id": "", "code": ""},
        "status": "5,2",
        "weight": "",
        "weightUnit": "",
        "weightUnitValue": "",
        "researchGroup": {"id": "1491301397488844801"},
        "isConfirmBirth": "1",
        "birthYear": "2025",
        "weightDate": "",
        "remarks": LARGE_CONTENT,
        "relevanceId": "",
        "version": "",
        "transferTime": "",
        "transferRemarks": "",
        "transferList": [],
        "otherVariety": "",
        "varietyType": "",
        "animalSource": "1",
        "enterTime": f"{random_date} 00:00:00",
        "animalModel": "",
        "project": {"id": "1825457771270230018"},
        "projectManager": {"id": "1810602921408172033"},
        "reminderTime": random_date,
        "reminderUser": {"id": "1463828311460319233"},
        "fileUrl": FILES_PATH,
        "groupChagneDate": random_date,
        "infantId": "",
        "statusStr": "5,2"
        }
async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES):
    attempt = 0
    last_err = None
    while attempt < max_retries:
        data = create_animal_data(index)
        start = time.time()
        try:
            async with session.post(url, json=data, headers=headers) as resp:
                text = await resp.text()
                latency_ms = (time.time() - start) * 1000.0
                status = resp.status
                if status == 200:
                    return {
                        'index': index,
                        'timestamp': time.time(),
                        'status_code': status,
                        'latency_ms': latency_ms,
                        'response_size': len(text) if text is not None else None,
                        'error': None
                    }
                else:
                    last_err = f'status_{status}:{text}'
                    attempt += 1
                    await asyncio.sleep(min(10, 2 ** attempt))
        except Exception as e:
            latency_ms = (time.time() - start) * 1000.0
            last_err = f'{type(e).__name__}:{str(e)}'
            attempt += 1
            await asyncio.sleep(min(10, 2 ** attempt))
    # 最终失败
    return {
        'index': index,
        'timestamp': time.time(),
        'status_code': 0,
        'latency_ms': latency_ms if 'latency_ms' in locals() else 0,
        'response_size': None,
        'error': last_err
    }
async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock):
    while True:
        idx = await queue.get()
        if idx is None:
            queue.task_done()
            break
        try:
            res = await perform_request(session, idx)
            # 记录到报告生成器
            gen.record_result(
                index=res['index'],
                timestamp=res['timestamp'],
                status_code=int(res['status_code']),
                latency_ms=float(res['latency_ms']),
                response_size=res.get('response_size'),
                error=res.get('error')
            )
            async with lock:
                if res['status_code'] and 200 <= res['status_code'] < 300:
                    success_counter['count'] += 1
                else:
                    failed_list.append((res['index'], res.get('error')))
                pbar.update(1)
        except Exception as e:
            async with lock:
                failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}'))
                pbar.update(1)
        finally:
            queue.task_done()
async def batch_create_animals(total: int, num_workers: int):
    # 动态加载报告生成器模块(支持中文文件名)
    gen = None
    try:
        import importlib.util
        script_dir = os.path.dirname(os.path.abspath(__file__))
        report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py')
        if os.path.exists(report_path):
            spec = importlib.util.spec_from_file_location('report_module', report_path)
            report_module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(report_module)
            LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator')
        else:
            # 备用:尝试直接导入模块名(若你的文件名已改为 ascii)
            from report_generator import LoadTestReportGenerator  # type: ignore
        gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告')
    except Exception as e:
        print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e)
        raise
    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
    connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False)
    async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
        queue = asyncio.Queue()
        for i in range(1, total + 1):
            await queue.put(i)
        for _ in range(num_workers):
            await queue.put(None)
        success_counter = {'count': 0}
        failed_list = []
        lock = asyncio.Lock()
        with tqdm(total=total, desc='创建进度') as pbar:
            workers = [
                asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock))
                for i in range(num_workers)
            ]
            await asyncio.gather(*workers)
        # 任务完成,生成报告
        os.makedirs(OUTPUT_DIR, exist_ok=True)
        outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx'])
        stats = gen.compute_stats()
        # 构造钉钉摘要消息(中文)
        now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}']
        msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}")
        msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}")
        lat = stats.get('latency_ms', {})
        msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}")
        # 列出生成的报告文件
        file_list = []
        for k, v in outputs.items():
            if k == 'charts':
                for cname, cpath in v.items():
                    file_list.append(os.path.abspath(cpath))
            else:
                file_list.append(os.path.abspath(v))
        msg.append('生成文件:')
        for p in file_list:
            msg.append(p)
        final_msg = '\n'.join(msg)
        # 发送钉钉消息
        try:
            dingtalk_helper.send_message(final_msg)
        except Exception as e:
            print('发送钉钉消息失败:', e)
        print('\n[SUMMARY] 已生成报告并发送钉钉摘要。')
        print('成功数:', success_counter['count'], ' 失败数:', len(failed_list))
        if failed_list:
            print('失败示例(最多显示50条):')
            for idx, err in failed_list[:50]:
                print(f'  #{idx} => {err}')
if __name__ == '__main__':
    # 运行前建议先用小规模测试
    TOTAL_REQUESTS = TOTAL_REQUESTS
    NUM_WORKERS = NUM_WORKERS
    asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS))
测试组/脚本/造数脚本2/昆明理工大学/临床检测.py
New file
Diff too large
测试组/脚本/造数脚本2/昆明理工大学/动物检疫.py
New file
Diff too large
测试组/脚本/造数脚本2/昆明理工大学/病毒检测.py
New file
@@ -0,0 +1,241 @@
"""
集成压测脚本(带压测报告生成并通过钉钉发送摘要)
说明:
 - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。
 - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。
 - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。
 - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选)
 - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。
"""
import sys
import os
# 将上一级目录加入模块搜索路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio
import aiohttp
import time
import traceback
import datetime
from tqdm import tqdm
from Util.random_util import RandomUtil
from Util.dingtalk_helper import DingTalkHelper
# --- 配置 ---
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
apiname = "病毒检测"
url = "http://192.168.6.168:5534/api/detection/virusdetection/virusDetection/save"
headers = {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2Nzk1MDksInVzZXJuYW1lIjoiZ2x5In0.aApZ-cXC_pIw6gdPZHg3TlsgZIaCPRrmlMDM3-zEhtg",
    "Content-Type": "application/json"
}
NUM_WORKERS = 100
TOTAL_REQUESTS = 100000
MAX_RETRIES = 3
REQUEST_TIMEOUT = 60
OUTPUT_DIR = './load_test_report'
# --- 初始化 ---
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
LARGE_CONTENT = "备注压测中" * 10
FILES_PATH = "/userfiles/1463828311460319233/程序附件//detection/virusdetection/virusDetection/2025/10/cs(2).jpg"
def create_animal_data(idx: int):
    random_code = RandomUtil.generate_random_number_string(0, 10000)
    random_code_grade = RandomUtil.generate_random_number_string(1, 2)
    random_code_lx = RandomUtil.generate_random_number_string(0, 3)
    random_date = RandomUtil.generate_random_date("2023-01-01", "2025-10-16")
    return {
          "id": "",
          "animal": {
            "id": "1978847173303963649"
          },
          "variety": "食蟹猴",
          "gender": "1",
          "age": 1.9,
          "weight": 8,
          "project": {
            "id": "1825457771270230018"
          },
          "checkTime": f"{random_date} 09:11:38",
          "collectTime": f"{random_date} 09:12:03",
          "moduleKey": "",
          "type": random_code_grade,
          "monkeyTLymphocyte": random_code_lx,
          "spv": random_code_lx,
          "siv": random_code_lx,
          "measlesVirusMv": random_code_lx,
          "bvirus": random_code_lx,
          "srv": random_code_lx,
          "fileUrl": FILES_PATH,
          "remarks": LARGE_CONTENT,
          "purpose": "2",
          "version": "",
          "grade": "2"
        }
async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES):
    attempt = 0
    last_err = None
    while attempt < max_retries:
        data = create_animal_data(index)
        start = time.time()
        try:
            async with session.post(url, json=data, headers=headers) as resp:
                text = await resp.text()
                latency_ms = (time.time() - start) * 1000.0
                status = resp.status
                if status == 200:
                    return {
                        'index': index,
                        'timestamp': time.time(),
                        'status_code': status,
                        'latency_ms': latency_ms,
                        'response_size': len(text) if text is not None else None,
                        'error': None
                    }
                else:
                    last_err = f'status_{status}:{text}'
                    attempt += 1
                    await asyncio.sleep(min(10, 2 ** attempt))
        except Exception as e:
            latency_ms = (time.time() - start) * 1000.0
            last_err = f'{type(e).__name__}:{str(e)}'
            attempt += 1
            await asyncio.sleep(min(10, 2 ** attempt))
    # 最终失败
    return {
        'index': index,
        'timestamp': time.time(),
        'status_code': 0,
        'latency_ms': latency_ms if 'latency_ms' in locals() else 0,
        'response_size': None,
        'error': last_err
    }
async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock):
    while True:
        idx = await queue.get()
        if idx is None:
            queue.task_done()
            break
        try:
            res = await perform_request(session, idx)
            # 记录到报告生成器
            gen.record_result(
                index=res['index'],
                timestamp=res['timestamp'],
                status_code=int(res['status_code']),
                latency_ms=float(res['latency_ms']),
                response_size=res.get('response_size'),
                error=res.get('error')
            )
            async with lock:
                if res['status_code'] and 200 <= res['status_code'] < 300:
                    success_counter['count'] += 1
                else:
                    failed_list.append((res['index'], res.get('error')))
                pbar.update(1)
        except Exception as e:
            async with lock:
                failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}'))
                pbar.update(1)
        finally:
            queue.task_done()
async def batch_create_animals(total: int, num_workers: int):
    # 动态加载报告生成器模块(支持中文文件名)
    gen = None
    try:
        import importlib.util
        script_dir = os.path.dirname(os.path.abspath(__file__))
        report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py')
        if os.path.exists(report_path):
            spec = importlib.util.spec_from_file_location('report_module', report_path)
            report_module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(report_module)
            LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator')
        else:
            # 备用:尝试直接导入模块名(若你的文件名已改为 ascii)
            from report_generator import LoadTestReportGenerator  # type: ignore
        gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告')
    except Exception as e:
        print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e)
        raise
    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
    connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False)
    async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
        queue = asyncio.Queue()
        for i in range(1, total + 1):
            await queue.put(i)
        for _ in range(num_workers):
            await queue.put(None)
        success_counter = {'count': 0}
        failed_list = []
        lock = asyncio.Lock()
        with tqdm(total=total, desc='创建进度') as pbar:
            workers = [
                asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock))
                for i in range(num_workers)
            ]
            await asyncio.gather(*workers)
        # 任务完成,生成报告
        os.makedirs(OUTPUT_DIR, exist_ok=True)
        outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx'])
        stats = gen.compute_stats()
        # 构造钉钉摘要消息(中文)
        now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}']
        msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}")
        msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}")
        lat = stats.get('latency_ms', {})
        msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}")
        # 列出生成的报告文件
        file_list = []
        for k, v in outputs.items():
            if k == 'charts':
                for cname, cpath in v.items():
                    file_list.append(os.path.abspath(cpath))
            else:
                file_list.append(os.path.abspath(v))
        msg.append('生成文件:')
        for p in file_list:
            msg.append(p)
        final_msg = '\n'.join(msg)
        # 发送钉钉消息
        try:
            dingtalk_helper.send_message(final_msg)
        except Exception as e:
            print('发送钉钉消息失败:', e)
        print('\n[SUMMARY] 已生成报告并发送钉钉摘要。')
        print('成功数:', success_counter['count'], ' 失败数:', len(failed_list))
        if failed_list:
            print('失败示例(最多显示50条):')
            for idx, err in failed_list[:50]:
                print(f'  #{idx} => {err}')
if __name__ == '__main__':
    # 运行前建议先用小规模测试
    TOTAL_REQUESTS = TOTAL_REQUESTS
    NUM_WORKERS = NUM_WORKERS
    asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS))
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544.details.csv
New file
@@ -0,0 +1,101 @@
index,timestamp,datetime,status_code,latency_ms,response_size,error
6,1760579736.5128722,2025-10-16 09:55:36,200,715.6155109405518,20,
1,1760579736.528873,2025-10-16 09:55:36,200,738.6245727539062,20,
8,1760579736.8814845,2025-10-16 09:55:36,200,1082.2265148162842,20,
7,1760579737.034483,2025-10-16 09:55:37,200,1236.2267971038818,20,
5,1760579737.072551,2025-10-16 09:55:37,200,1276.2954235076904,20,
19,1760579737.0930712,2025-10-16 09:55:37,200,1285.822868347168,20,
2,1760579737.28608,2025-10-16 09:55:37,200,1493.8290119171143,20,
18,1760579737.4374523,2025-10-16 09:55:37,200,1631.2031745910645,20,
3,1760579737.4674568,2025-10-16 09:55:37,200,1674.2029190063477,20,
10,1760579737.5387669,2025-10-16 09:55:37,200,1737.511157989502,20,
4,1760579737.569851,2025-10-16 09:55:37,200,1775.5978107452393,20,
14,1760579737.6775362,2025-10-16 09:55:37,200,1873.2872009277344,20,
44,1760579737.8772526,2025-10-16 09:55:37,200,2055.0029277801514,20,
52,1760579738.0004578,2025-10-16 09:55:38,200,2174.2074489593506,20,
40,1760579738.0458317,2025-10-16 09:55:38,200,2226.5822887420654,20,
36,1760579738.0685692,2025-10-16 09:55:38,200,2251.3201236724854,20,
16,1760579738.1789966,2025-10-16 09:55:38,200,2373.7473487854004,20,
54,1760579738.262952,2025-10-16 09:55:38,200,2433.703899383545,20,
13,1760579738.4627268,2025-10-16 09:55:38,200,2659.477710723877,19,
12,1760579738.469727,2025-10-16 09:55:38,200,2666.477918624878,20,
37,1760579738.5605764,2025-10-16 09:55:38,200,2742.3269748687744,20,
47,1760579738.608516,2025-10-16 09:55:38,200,2785.266637802124,20,
58,1760579738.793892,2025-10-16 09:55:38,200,2962.644100189209,20,
48,1760579738.8212566,2025-10-16 09:55:38,200,2997.007131576538,20,
46,1760579738.9038382,2025-10-16 09:55:38,200,3080.5888175964355,20,
26,1760579738.9655027,2025-10-16 09:55:38,200,3154.2539596557617,20,
43,1760579739.062388,2025-10-16 09:55:39,200,3241.1389350891113,20,
34,1760579739.149593,2025-10-16 09:55:39,200,3333.3449363708496,20,
45,1760579739.3271666,2025-10-16 09:55:39,200,3504.9169063568115,20,
41,1760579739.3321707,2025-10-16 09:55:39,200,3511.9237899780273,20,
25,1760579739.344079,2025-10-16 09:55:39,200,3533.830165863037,20,
51,1760579739.4308379,2025-10-16 09:55:39,200,3604.5875549316406,20,
29,1760579739.536484,2025-10-16 09:55:39,200,3723.2346534729004,20,
84,1760579739.630485,2025-10-16 09:55:39,200,3784.235954284668,20,
20,1760579739.776465,2025-10-16 09:55:39,200,3969.2165851593018,20,
15,1760579739.8412845,2025-10-16 09:55:39,200,4037.0354652404785,20,
31,1760579739.8695781,2025-10-16 09:55:39,200,4055.328845977783,20,
60,1760579739.892454,2025-10-16 09:55:39,200,4060.203790664673,20,
27,1760579740.0023754,2025-10-16 09:55:40,200,4191.126585006714,20,
30,1760579740.145428,2025-10-16 09:55:40,200,4332.178592681885,20,
53,1760579740.1794794,2025-10-16 09:55:40,200,4351.231336593628,20,
42,1760579740.366609,2025-10-16 09:55:40,200,4546.3621616363525,20,
71,1760579740.366609,2025-10-16 09:55:40,200,4528.359413146973,20,
75,1760579740.4744072,2025-10-16 09:55:40,200,4633.156538009644,20,
66,1760579740.5180902,2025-10-16 09:55:40,200,4681.840181350708,20,
62,1760579740.6459935,2025-10-16 09:55:40,200,4812.745571136475,20,
64,1760579740.6745234,2025-10-16 09:55:40,200,4840.275526046753,20,
72,1760579740.8360484,2025-10-16 09:55:40,200,4996.798753738403,20,
33,1760579740.8478594,2025-10-16 09:55:40,200,5032.6104164123535,20,
24,1760579740.961691,2025-10-16 09:55:40,200,5151.442050933838,20,
39,1760579740.9853978,2025-10-16 09:55:40,200,5166.14842414856,20,
92,1760579741.1095653,2025-10-16 09:55:41,200,5259.315013885498,20,
95,1760579741.17415,2025-10-16 09:55:41,200,5321.899175643921,20,
97,1760579741.2893555,2025-10-16 09:55:41,200,5436.105251312256,20,
28,1760579741.3184445,2025-10-16 09:55:41,200,5506.195545196533,20,
70,1760579741.4219213,2025-10-16 09:55:41,200,5583.671569824219,20,
80,1760579741.4306922,2025-10-16 09:55:41,200,5587.444305419922,20,
73,1760579741.5471199,2025-10-16 09:55:41,200,5707.870244979858,20,
93,1760579741.6452277,2025-10-16 09:55:41,200,5793.977737426758,20,
94,1760579741.7387617,2025-10-16 09:55:41,200,5887.511730194092,19,
49,1760579741.79457,2025-10-16 09:55:41,200,5969.320774078369,20,
89,1760579741.8575878,2025-10-16 09:55:41,200,6009.339809417725,20,
32,1760579741.8911214,2025-10-16 09:55:41,200,6076.872110366821,20,
38,1760579741.9916003,2025-10-16 09:55:41,200,6173.350811004639,20,
96,1760579742.1457305,2025-10-16 09:55:42,200,6293.479681015015,20,
98,1760579742.1986532,2025-10-16 09:55:42,200,6345.402956008911,20,
76,1760579742.304511,2025-10-16 09:55:42,200,6463.2604122161865,20,
81,1760579742.3438184,2025-10-16 09:55:42,200,6499.570608139038,20,
77,1760579742.370017,2025-10-16 09:55:42,200,6527.769088745117,20,
57,1760579742.4302893,2025-10-16 09:55:42,200,6599.04146194458,20,
22,1760579742.6512027,2025-10-16 09:55:42,200,6842.953681945801,20,
78,1760579742.6552012,2025-10-16 09:55:42,200,6812.953233718872,20,
83,1760579742.7378995,2025-10-16 09:55:42,200,6892.650604248047,20,
88,1760579742.771665,2025-10-16 09:55:42,200,6923.417091369629,20,
99,1760579742.813827,2025-10-16 09:55:42,200,6959.575176239014,20,
50,1760579742.8238244,2025-10-16 09:55:42,200,6998.575210571289,20,
87,1760579743.0883355,2025-10-16 09:55:43,200,7241.0852909088135,20,
82,1760579743.1234865,2025-10-16 09:55:43,200,7279.238700866699,20,
21,1760579743.161384,2025-10-16 09:55:43,200,7353.135108947754,20,
90,1760579743.1896164,2025-10-16 09:55:43,200,7340.368270874023,20,
35,1760579743.1936219,2025-10-16 09:55:43,200,7377.373695373535,19,
91,1760579743.2927885,2025-10-16 09:55:43,200,7443.540334701538,20,
68,1760579743.5564194,2025-10-16 09:55:43,200,7719.170093536377,20,
61,1760579743.6013098,2025-10-16 09:55:43,200,7768.061876296997,20,
17,1760579743.6033225,2025-10-16 09:55:43,200,7798.073291778564,20,
63,1760579743.611075,2025-10-16 09:55:43,200,7776.827096939087,20,
9,1760579743.621174,2025-10-16 09:55:43,200,7819.918394088745,20,
55,1760579743.7662518,2025-10-16 09:55:43,200,7937.003612518311,20,
67,1760579743.999791,2025-10-16 09:55:43,200,8163.540840148926,20,
85,1760579744.0067902,2025-10-16 09:55:44,200,8160.54105758667,20,
100,1760579744.0268416,2025-10-16 09:55:44,200,8172.5897789001465,20,
74,1760579744.0278468,2025-10-16 09:55:44,200,8187.59822845459,20,
59,1760579744.0623982,2025-10-16 09:55:44,200,8230.148077011108,20,
65,1760579744.2366185,2025-10-16 09:55:44,200,8401.370763778687,19,
86,1760579744.4280767,2025-10-16 09:55:44,200,8580.8265209198,20,
56,1760579744.434209,2025-10-16 09:55:44,200,8603.960990905762,20,
23,1760579744.5166273,2025-10-16 09:55:44,200,8707.377433776855,20,
11,1760579744.521632,2025-10-16 09:55:44,200,8719.382762908936,20,
79,1760579744.5326326,2025-10-16 09:55:44,200,8689.384698867798,20,
69,1760579744.7850199,2025-10-16 09:55:44,200,8947.770595550537,20,
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544.html
New file
@@ -0,0 +1,50 @@
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>压测详细报告</title>
          <style>
            body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
            h2{color:#2c3e50}
            table{border-collapse:collapse; width:100%}
            th,td{padding:6px; text-align:left}
          </style>
        </head>
        <body>
          <h1>压测详细报告</h1>
        <h2>摘要</h2>
        <ul>
          <li>报告名称:压测详细报告</li>
          <li>生成时间:2025-10-16 09:55:45</li>
          <li>总请求数:100</li>
          <li>成功数:100,失败数:0,成功率:100.00%</li>
          <li>总耗时(秒):8.27</li>
          <li>平均吞吐(req/s):12.09</li>
        </ul>
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:715.62</li>
          <li>最大:8947.77</li>
          <li>平均:5055.96</li>
          <li>中位数(P50):5158.80</li>
          <li>P90:8164.45,P95:8581.98,P99:8721.67</li>
        </ul>
          <h2>状态码分布</h2><ul><li>200: 100</li></ul>
          <h2>错误汇总</h2><p>无错误记录</p>
          <h2>图表</h2><div><h3>latency_hist</h3><img src="压测任务_20251016_095544_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="压测任务_20251016_095544_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          <tr><td>6</td><td>2025-10-16 09:55:36</td><td>200</td><td>715.6155109405518</td><td>20</td><td></td></tr><tr><td>1</td><td>2025-10-16 09:55:36</td><td>200</td><td>738.6245727539062</td><td>20</td><td></td></tr><tr><td>8</td><td>2025-10-16 09:55:36</td><td>200</td><td>1082.2265148162842</td><td>20</td><td></td></tr><tr><td>7</td><td>2025-10-16 09:55:37</td><td>200</td><td>1236.2267971038818</td><td>20</td><td></td></tr><tr><td>5</td><td>2025-10-16 09:55:37</td><td>200</td><td>1276.2954235076904</td><td>20</td><td></td></tr><tr><td>19</td><td>2025-10-16 09:55:37</td><td>200</td><td>1285.822868347168</td><td>20</td><td></td></tr><tr><td>2</td><td>2025-10-16 09:55:37</td><td>200</td><td>1493.8290119171143</td><td>20</td><td></td></tr><tr><td>18</td><td>2025-10-16 09:55:37</td><td>200</td><td>1631.2031745910645</td><td>20</td><td></td></tr><tr><td>3</td><td>2025-10-16 09:55:37</td><td>200</td><td>1674.2029190063477</td><td>20</td><td></td></tr><tr><td>10</td><td>2025-10-16 09:55:37</td><td>200</td><td>1737.511157989502</td><td>20</td><td></td></tr><tr><td>4</td><td>2025-10-16 09:55:37</td><td>200</td><td>1775.5978107452393</td><td>20</td><td></td></tr><tr><td>14</td><td>2025-10-16 09:55:37</td><td>200</td><td>1873.2872009277344</td><td>20</td><td></td></tr><tr><td>44</td><td>2025-10-16 09:55:37</td><td>200</td><td>2055.0029277801514</td><td>20</td><td></td></tr><tr><td>52</td><td>2025-10-16 09:55:38</td><td>200</td><td>2174.2074489593506</td><td>20</td><td></td></tr><tr><td>40</td><td>2025-10-16 09:55:38</td><td>200</td><td>2226.5822887420654</td><td>20</td><td></td></tr><tr><td>36</td><td>2025-10-16 09:55:38</td><td>200</td><td>2251.3201236724854</td><td>20</td><td></td></tr><tr><td>16</td><td>2025-10-16 09:55:38</td><td>200</td><td>2373.7473487854004</td><td>20</td><td></td></tr><tr><td>54</td><td>2025-10-16 09:55:38</td><td>200</td><td>2433.703899383545</td><td>20</td><td></td></tr><tr><td>13</td><td>2025-10-16 09:55:38</td><td>200</td><td>2659.477710723877</td><td>19</td><td></td></tr><tr><td>12</td><td>2025-10-16 09:55:38</td><td>200</td><td>2666.477918624878</td><td>20</td><td></td></tr><tr><td>37</td><td>2025-10-16 09:55:38</td><td>200</td><td>2742.3269748687744</td><td>20</td><td></td></tr><tr><td>47</td><td>2025-10-16 09:55:38</td><td>200</td><td>2785.266637802124</td><td>20</td><td></td></tr><tr><td>58</td><td>2025-10-16 09:55:38</td><td>200</td><td>2962.644100189209</td><td>20</td><td></td></tr><tr><td>48</td><td>2025-10-16 09:55:38</td><td>200</td><td>2997.007131576538</td><td>20</td><td></td></tr><tr><td>46</td><td>2025-10-16 09:55:38</td><td>200</td><td>3080.5888175964355</td><td>20</td><td></td></tr><tr><td>26</td><td>2025-10-16 09:55:38</td><td>200</td><td>3154.2539596557617</td><td>20</td><td></td></tr><tr><td>43</td><td>2025-10-16 09:55:39</td><td>200</td><td>3241.1389350891113</td><td>20</td><td></td></tr><tr><td>34</td><td>2025-10-16 09:55:39</td><td>200</td><td>3333.3449363708496</td><td>20</td><td></td></tr><tr><td>45</td><td>2025-10-16 09:55:39</td><td>200</td><td>3504.9169063568115</td><td>20</td><td></td></tr><tr><td>41</td><td>2025-10-16 09:55:39</td><td>200</td><td>3511.9237899780273</td><td>20</td><td></td></tr><tr><td>25</td><td>2025-10-16 09:55:39</td><td>200</td><td>3533.830165863037</td><td>20</td><td></td></tr><tr><td>51</td><td>2025-10-16 09:55:39</td><td>200</td><td>3604.5875549316406</td><td>20</td><td></td></tr><tr><td>29</td><td>2025-10-16 09:55:39</td><td>200</td><td>3723.2346534729004</td><td>20</td><td></td></tr><tr><td>84</td><td>2025-10-16 09:55:39</td><td>200</td><td>3784.235954284668</td><td>20</td><td></td></tr><tr><td>20</td><td>2025-10-16 09:55:39</td><td>200</td><td>3969.2165851593018</td><td>20</td><td></td></tr><tr><td>15</td><td>2025-10-16 09:55:39</td><td>200</td><td>4037.0354652404785</td><td>20</td><td></td></tr><tr><td>31</td><td>2025-10-16 09:55:39</td><td>200</td><td>4055.328845977783</td><td>20</td><td></td></tr><tr><td>60</td><td>2025-10-16 09:55:39</td><td>200</td><td>4060.203790664673</td><td>20</td><td></td></tr><tr><td>27</td><td>2025-10-16 09:55:40</td><td>200</td><td>4191.126585006714</td><td>20</td><td></td></tr><tr><td>30</td><td>2025-10-16 09:55:40</td><td>200</td><td>4332.178592681885</td><td>20</td><td></td></tr><tr><td>53</td><td>2025-10-16 09:55:40</td><td>200</td><td>4351.231336593628</td><td>20</td><td></td></tr><tr><td>42</td><td>2025-10-16 09:55:40</td><td>200</td><td>4546.3621616363525</td><td>20</td><td></td></tr><tr><td>71</td><td>2025-10-16 09:55:40</td><td>200</td><td>4528.359413146973</td><td>20</td><td></td></tr><tr><td>75</td><td>2025-10-16 09:55:40</td><td>200</td><td>4633.156538009644</td><td>20</td><td></td></tr><tr><td>66</td><td>2025-10-16 09:55:40</td><td>200</td><td>4681.840181350708</td><td>20</td><td></td></tr><tr><td>62</td><td>2025-10-16 09:55:40</td><td>200</td><td>4812.745571136475</td><td>20</td><td></td></tr><tr><td>64</td><td>2025-10-16 09:55:40</td><td>200</td><td>4840.275526046753</td><td>20</td><td></td></tr><tr><td>72</td><td>2025-10-16 09:55:40</td><td>200</td><td>4996.798753738403</td><td>20</td><td></td></tr><tr><td>33</td><td>2025-10-16 09:55:40</td><td>200</td><td>5032.6104164123535</td><td>20</td><td></td></tr><tr><td>24</td><td>2025-10-16 09:55:40</td><td>200</td><td>5151.442050933838</td><td>20</td><td></td></tr><tr><td>39</td><td>2025-10-16 09:55:40</td><td>200</td><td>5166.14842414856</td><td>20</td><td></td></tr><tr><td>92</td><td>2025-10-16 09:55:41</td><td>200</td><td>5259.315013885498</td><td>20</td><td></td></tr><tr><td>95</td><td>2025-10-16 09:55:41</td><td>200</td><td>5321.899175643921</td><td>20</td><td></td></tr><tr><td>97</td><td>2025-10-16 09:55:41</td><td>200</td><td>5436.105251312256</td><td>20</td><td></td></tr><tr><td>28</td><td>2025-10-16 09:55:41</td><td>200</td><td>5506.195545196533</td><td>20</td><td></td></tr><tr><td>70</td><td>2025-10-16 09:55:41</td><td>200</td><td>5583.671569824219</td><td>20</td><td></td></tr><tr><td>80</td><td>2025-10-16 09:55:41</td><td>200</td><td>5587.444305419922</td><td>20</td><td></td></tr><tr><td>73</td><td>2025-10-16 09:55:41</td><td>200</td><td>5707.870244979858</td><td>20</td><td></td></tr><tr><td>93</td><td>2025-10-16 09:55:41</td><td>200</td><td>5793.977737426758</td><td>20</td><td></td></tr><tr><td>94</td><td>2025-10-16 09:55:41</td><td>200</td><td>5887.511730194092</td><td>19</td><td></td></tr><tr><td>49</td><td>2025-10-16 09:55:41</td><td>200</td><td>5969.320774078369</td><td>20</td><td></td></tr><tr><td>89</td><td>2025-10-16 09:55:41</td><td>200</td><td>6009.339809417725</td><td>20</td><td></td></tr><tr><td>32</td><td>2025-10-16 09:55:41</td><td>200</td><td>6076.872110366821</td><td>20</td><td></td></tr><tr><td>38</td><td>2025-10-16 09:55:41</td><td>200</td><td>6173.350811004639</td><td>20</td><td></td></tr><tr><td>96</td><td>2025-10-16 09:55:42</td><td>200</td><td>6293.479681015015</td><td>20</td><td></td></tr><tr><td>98</td><td>2025-10-16 09:55:42</td><td>200</td><td>6345.402956008911</td><td>20</td><td></td></tr><tr><td>76</td><td>2025-10-16 09:55:42</td><td>200</td><td>6463.2604122161865</td><td>20</td><td></td></tr><tr><td>81</td><td>2025-10-16 09:55:42</td><td>200</td><td>6499.570608139038</td><td>20</td><td></td></tr><tr><td>77</td><td>2025-10-16 09:55:42</td><td>200</td><td>6527.769088745117</td><td>20</td><td></td></tr><tr><td>57</td><td>2025-10-16 09:55:42</td><td>200</td><td>6599.04146194458</td><td>20</td><td></td></tr><tr><td>22</td><td>2025-10-16 09:55:42</td><td>200</td><td>6842.953681945801</td><td>20</td><td></td></tr><tr><td>78</td><td>2025-10-16 09:55:42</td><td>200</td><td>6812.953233718872</td><td>20</td><td></td></tr><tr><td>83</td><td>2025-10-16 09:55:42</td><td>200</td><td>6892.650604248047</td><td>20</td><td></td></tr><tr><td>88</td><td>2025-10-16 09:55:42</td><td>200</td><td>6923.417091369629</td><td>20</td><td></td></tr><tr><td>99</td><td>2025-10-16 09:55:42</td><td>200</td><td>6959.575176239014</td><td>20</td><td></td></tr><tr><td>50</td><td>2025-10-16 09:55:42</td><td>200</td><td>6998.575210571289</td><td>20</td><td></td></tr><tr><td>87</td><td>2025-10-16 09:55:43</td><td>200</td><td>7241.0852909088135</td><td>20</td><td></td></tr><tr><td>82</td><td>2025-10-16 09:55:43</td><td>200</td><td>7279.238700866699</td><td>20</td><td></td></tr><tr><td>21</td><td>2025-10-16 09:55:43</td><td>200</td><td>7353.135108947754</td><td>20</td><td></td></tr><tr><td>90</td><td>2025-10-16 09:55:43</td><td>200</td><td>7340.368270874023</td><td>20</td><td></td></tr><tr><td>35</td><td>2025-10-16 09:55:43</td><td>200</td><td>7377.373695373535</td><td>19</td><td></td></tr><tr><td>91</td><td>2025-10-16 09:55:43</td><td>200</td><td>7443.540334701538</td><td>20</td><td></td></tr><tr><td>68</td><td>2025-10-16 09:55:43</td><td>200</td><td>7719.170093536377</td><td>20</td><td></td></tr><tr><td>61</td><td>2025-10-16 09:55:43</td><td>200</td><td>7768.061876296997</td><td>20</td><td></td></tr><tr><td>17</td><td>2025-10-16 09:55:43</td><td>200</td><td>7798.073291778564</td><td>20</td><td></td></tr><tr><td>63</td><td>2025-10-16 09:55:43</td><td>200</td><td>7776.827096939087</td><td>20</td><td></td></tr><tr><td>9</td><td>2025-10-16 09:55:43</td><td>200</td><td>7819.918394088745</td><td>20</td><td></td></tr><tr><td>55</td><td>2025-10-16 09:55:43</td><td>200</td><td>7937.003612518311</td><td>20</td><td></td></tr><tr><td>67</td><td>2025-10-16 09:55:43</td><td>200</td><td>8163.540840148926</td><td>20</td><td></td></tr><tr><td>85</td><td>2025-10-16 09:55:44</td><td>200</td><td>8160.54105758667</td><td>20</td><td></td></tr><tr><td>100</td><td>2025-10-16 09:55:44</td><td>200</td><td>8172.5897789001465</td><td>20</td><td></td></tr><tr><td>74</td><td>2025-10-16 09:55:44</td><td>200</td><td>8187.59822845459</td><td>20</td><td></td></tr><tr><td>59</td><td>2025-10-16 09:55:44</td><td>200</td><td>8230.148077011108</td><td>20</td><td></td></tr><tr><td>65</td><td>2025-10-16 09:55:44</td><td>200</td><td>8401.370763778687</td><td>19</td><td></td></tr><tr><td>86</td><td>2025-10-16 09:55:44</td><td>200</td><td>8580.8265209198</td><td>20</td><td></td></tr><tr><td>56</td><td>2025-10-16 09:55:44</td><td>200</td><td>8603.960990905762</td><td>20</td><td></td></tr><tr><td>23</td><td>2025-10-16 09:55:44</td><td>200</td><td>8707.377433776855</td><td>20</td><td></td></tr><tr><td>11</td><td>2025-10-16 09:55:44</td><td>200</td><td>8719.382762908936</td><td>20</td><td></td></tr><tr><td>79</td><td>2025-10-16 09:55:44</td><td>200</td><td>8689.384698867798</td><td>20</td><td></td></tr><tr><td>69</td><td>2025-10-16 09:55:44</td><td>200</td><td>8947.770595550537</td><td>20</td><td></td></tr>
        </table>
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544.summary.json
New file
@@ -0,0 +1,963 @@
{
  "stats": {
    "total_requests": 100,
    "success_count": 100,
    "fail_count": 0,
    "success_rate": 1.0,
    "duration_seconds": 8.27214765548706,
    "throughput_rps": 12.088759070163388,
    "latency_ms": {
      "min": 715.6155109405518,
      "max": 8947.770595550537,
      "avg": 5055.96134185791,
      "median": 5158.795237541199,
      "p90": 8164.445734024048,
      "p95": 8581.983244419098,
      "p99": 8721.666641235353
    },
    "status_groups": {
      "200": 100
    },
    "error_summary": {},
    "rps_series": [
      [
        1760579736,
        3
      ],
      [
        1760579737,
        10
      ],
      [
        1760579738,
        13
      ],
      [
        1760579739,
        12
      ],
      [
        1760579740,
        13
      ],
      [
        1760579741,
        13
      ],
      [
        1760579742,
        12
      ],
      [
        1760579743,
        13
      ],
      [
        1760579744,
        11
      ]
    ]
  },
  "records": [
    {
      "index": 6,
      "timestamp": 1760579736.5128722,
      "datetime": "2025-10-16 09:55:36",
      "status_code": 200,
      "latency_ms": 715.6155109405518,
      "response_size": 20,
      "error": null
    },
    {
      "index": 1,
      "timestamp": 1760579736.528873,
      "datetime": "2025-10-16 09:55:36",
      "status_code": 200,
      "latency_ms": 738.6245727539062,
      "response_size": 20,
      "error": null
    },
    {
      "index": 8,
      "timestamp": 1760579736.8814845,
      "datetime": "2025-10-16 09:55:36",
      "status_code": 200,
      "latency_ms": 1082.2265148162842,
      "response_size": 20,
      "error": null
    },
    {
      "index": 7,
      "timestamp": 1760579737.034483,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1236.2267971038818,
      "response_size": 20,
      "error": null
    },
    {
      "index": 5,
      "timestamp": 1760579737.072551,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1276.2954235076904,
      "response_size": 20,
      "error": null
    },
    {
      "index": 19,
      "timestamp": 1760579737.0930712,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1285.822868347168,
      "response_size": 20,
      "error": null
    },
    {
      "index": 2,
      "timestamp": 1760579737.28608,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1493.8290119171143,
      "response_size": 20,
      "error": null
    },
    {
      "index": 18,
      "timestamp": 1760579737.4374523,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1631.2031745910645,
      "response_size": 20,
      "error": null
    },
    {
      "index": 3,
      "timestamp": 1760579737.4674568,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1674.2029190063477,
      "response_size": 20,
      "error": null
    },
    {
      "index": 10,
      "timestamp": 1760579737.5387669,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1737.511157989502,
      "response_size": 20,
      "error": null
    },
    {
      "index": 4,
      "timestamp": 1760579737.569851,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1775.5978107452393,
      "response_size": 20,
      "error": null
    },
    {
      "index": 14,
      "timestamp": 1760579737.6775362,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 1873.2872009277344,
      "response_size": 20,
      "error": null
    },
    {
      "index": 44,
      "timestamp": 1760579737.8772526,
      "datetime": "2025-10-16 09:55:37",
      "status_code": 200,
      "latency_ms": 2055.0029277801514,
      "response_size": 20,
      "error": null
    },
    {
      "index": 52,
      "timestamp": 1760579738.0004578,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2174.2074489593506,
      "response_size": 20,
      "error": null
    },
    {
      "index": 40,
      "timestamp": 1760579738.0458317,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2226.5822887420654,
      "response_size": 20,
      "error": null
    },
    {
      "index": 36,
      "timestamp": 1760579738.0685692,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2251.3201236724854,
      "response_size": 20,
      "error": null
    },
    {
      "index": 16,
      "timestamp": 1760579738.1789966,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2373.7473487854004,
      "response_size": 20,
      "error": null
    },
    {
      "index": 54,
      "timestamp": 1760579738.262952,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2433.703899383545,
      "response_size": 20,
      "error": null
    },
    {
      "index": 13,
      "timestamp": 1760579738.4627268,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2659.477710723877,
      "response_size": 19,
      "error": null
    },
    {
      "index": 12,
      "timestamp": 1760579738.469727,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2666.477918624878,
      "response_size": 20,
      "error": null
    },
    {
      "index": 37,
      "timestamp": 1760579738.5605764,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2742.3269748687744,
      "response_size": 20,
      "error": null
    },
    {
      "index": 47,
      "timestamp": 1760579738.608516,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2785.266637802124,
      "response_size": 20,
      "error": null
    },
    {
      "index": 58,
      "timestamp": 1760579738.793892,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2962.644100189209,
      "response_size": 20,
      "error": null
    },
    {
      "index": 48,
      "timestamp": 1760579738.8212566,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 2997.007131576538,
      "response_size": 20,
      "error": null
    },
    {
      "index": 46,
      "timestamp": 1760579738.9038382,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 3080.5888175964355,
      "response_size": 20,
      "error": null
    },
    {
      "index": 26,
      "timestamp": 1760579738.9655027,
      "datetime": "2025-10-16 09:55:38",
      "status_code": 200,
      "latency_ms": 3154.2539596557617,
      "response_size": 20,
      "error": null
    },
    {
      "index": 43,
      "timestamp": 1760579739.062388,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3241.1389350891113,
      "response_size": 20,
      "error": null
    },
    {
      "index": 34,
      "timestamp": 1760579739.149593,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3333.3449363708496,
      "response_size": 20,
      "error": null
    },
    {
      "index": 45,
      "timestamp": 1760579739.3271666,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3504.9169063568115,
      "response_size": 20,
      "error": null
    },
    {
      "index": 41,
      "timestamp": 1760579739.3321707,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3511.9237899780273,
      "response_size": 20,
      "error": null
    },
    {
      "index": 25,
      "timestamp": 1760579739.344079,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3533.830165863037,
      "response_size": 20,
      "error": null
    },
    {
      "index": 51,
      "timestamp": 1760579739.4308379,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3604.5875549316406,
      "response_size": 20,
      "error": null
    },
    {
      "index": 29,
      "timestamp": 1760579739.536484,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3723.2346534729004,
      "response_size": 20,
      "error": null
    },
    {
      "index": 84,
      "timestamp": 1760579739.630485,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3784.235954284668,
      "response_size": 20,
      "error": null
    },
    {
      "index": 20,
      "timestamp": 1760579739.776465,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 3969.2165851593018,
      "response_size": 20,
      "error": null
    },
    {
      "index": 15,
      "timestamp": 1760579739.8412845,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 4037.0354652404785,
      "response_size": 20,
      "error": null
    },
    {
      "index": 31,
      "timestamp": 1760579739.8695781,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 4055.328845977783,
      "response_size": 20,
      "error": null
    },
    {
      "index": 60,
      "timestamp": 1760579739.892454,
      "datetime": "2025-10-16 09:55:39",
      "status_code": 200,
      "latency_ms": 4060.203790664673,
      "response_size": 20,
      "error": null
    },
    {
      "index": 27,
      "timestamp": 1760579740.0023754,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4191.126585006714,
      "response_size": 20,
      "error": null
    },
    {
      "index": 30,
      "timestamp": 1760579740.145428,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4332.178592681885,
      "response_size": 20,
      "error": null
    },
    {
      "index": 53,
      "timestamp": 1760579740.1794794,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4351.231336593628,
      "response_size": 20,
      "error": null
    },
    {
      "index": 42,
      "timestamp": 1760579740.366609,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4546.3621616363525,
      "response_size": 20,
      "error": null
    },
    {
      "index": 71,
      "timestamp": 1760579740.366609,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4528.359413146973,
      "response_size": 20,
      "error": null
    },
    {
      "index": 75,
      "timestamp": 1760579740.4744072,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4633.156538009644,
      "response_size": 20,
      "error": null
    },
    {
      "index": 66,
      "timestamp": 1760579740.5180902,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4681.840181350708,
      "response_size": 20,
      "error": null
    },
    {
      "index": 62,
      "timestamp": 1760579740.6459935,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4812.745571136475,
      "response_size": 20,
      "error": null
    },
    {
      "index": 64,
      "timestamp": 1760579740.6745234,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4840.275526046753,
      "response_size": 20,
      "error": null
    },
    {
      "index": 72,
      "timestamp": 1760579740.8360484,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 4996.798753738403,
      "response_size": 20,
      "error": null
    },
    {
      "index": 33,
      "timestamp": 1760579740.8478594,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 5032.6104164123535,
      "response_size": 20,
      "error": null
    },
    {
      "index": 24,
      "timestamp": 1760579740.961691,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 5151.442050933838,
      "response_size": 20,
      "error": null
    },
    {
      "index": 39,
      "timestamp": 1760579740.9853978,
      "datetime": "2025-10-16 09:55:40",
      "status_code": 200,
      "latency_ms": 5166.14842414856,
      "response_size": 20,
      "error": null
    },
    {
      "index": 92,
      "timestamp": 1760579741.1095653,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5259.315013885498,
      "response_size": 20,
      "error": null
    },
    {
      "index": 95,
      "timestamp": 1760579741.17415,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5321.899175643921,
      "response_size": 20,
      "error": null
    },
    {
      "index": 97,
      "timestamp": 1760579741.2893555,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5436.105251312256,
      "response_size": 20,
      "error": null
    },
    {
      "index": 28,
      "timestamp": 1760579741.3184445,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5506.195545196533,
      "response_size": 20,
      "error": null
    },
    {
      "index": 70,
      "timestamp": 1760579741.4219213,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5583.671569824219,
      "response_size": 20,
      "error": null
    },
    {
      "index": 80,
      "timestamp": 1760579741.4306922,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5587.444305419922,
      "response_size": 20,
      "error": null
    },
    {
      "index": 73,
      "timestamp": 1760579741.5471199,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5707.870244979858,
      "response_size": 20,
      "error": null
    },
    {
      "index": 93,
      "timestamp": 1760579741.6452277,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5793.977737426758,
      "response_size": 20,
      "error": null
    },
    {
      "index": 94,
      "timestamp": 1760579741.7387617,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5887.511730194092,
      "response_size": 19,
      "error": null
    },
    {
      "index": 49,
      "timestamp": 1760579741.79457,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 5969.320774078369,
      "response_size": 20,
      "error": null
    },
    {
      "index": 89,
      "timestamp": 1760579741.8575878,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 6009.339809417725,
      "response_size": 20,
      "error": null
    },
    {
      "index": 32,
      "timestamp": 1760579741.8911214,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 6076.872110366821,
      "response_size": 20,
      "error": null
    },
    {
      "index": 38,
      "timestamp": 1760579741.9916003,
      "datetime": "2025-10-16 09:55:41",
      "status_code": 200,
      "latency_ms": 6173.350811004639,
      "response_size": 20,
      "error": null
    },
    {
      "index": 96,
      "timestamp": 1760579742.1457305,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6293.479681015015,
      "response_size": 20,
      "error": null
    },
    {
      "index": 98,
      "timestamp": 1760579742.1986532,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6345.402956008911,
      "response_size": 20,
      "error": null
    },
    {
      "index": 76,
      "timestamp": 1760579742.304511,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6463.2604122161865,
      "response_size": 20,
      "error": null
    },
    {
      "index": 81,
      "timestamp": 1760579742.3438184,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6499.570608139038,
      "response_size": 20,
      "error": null
    },
    {
      "index": 77,
      "timestamp": 1760579742.370017,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6527.769088745117,
      "response_size": 20,
      "error": null
    },
    {
      "index": 57,
      "timestamp": 1760579742.4302893,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6599.04146194458,
      "response_size": 20,
      "error": null
    },
    {
      "index": 22,
      "timestamp": 1760579742.6512027,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6842.953681945801,
      "response_size": 20,
      "error": null
    },
    {
      "index": 78,
      "timestamp": 1760579742.6552012,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6812.953233718872,
      "response_size": 20,
      "error": null
    },
    {
      "index": 83,
      "timestamp": 1760579742.7378995,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6892.650604248047,
      "response_size": 20,
      "error": null
    },
    {
      "index": 88,
      "timestamp": 1760579742.771665,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6923.417091369629,
      "response_size": 20,
      "error": null
    },
    {
      "index": 99,
      "timestamp": 1760579742.813827,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6959.575176239014,
      "response_size": 20,
      "error": null
    },
    {
      "index": 50,
      "timestamp": 1760579742.8238244,
      "datetime": "2025-10-16 09:55:42",
      "status_code": 200,
      "latency_ms": 6998.575210571289,
      "response_size": 20,
      "error": null
    },
    {
      "index": 87,
      "timestamp": 1760579743.0883355,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7241.0852909088135,
      "response_size": 20,
      "error": null
    },
    {
      "index": 82,
      "timestamp": 1760579743.1234865,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7279.238700866699,
      "response_size": 20,
      "error": null
    },
    {
      "index": 21,
      "timestamp": 1760579743.161384,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7353.135108947754,
      "response_size": 20,
      "error": null
    },
    {
      "index": 90,
      "timestamp": 1760579743.1896164,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7340.368270874023,
      "response_size": 20,
      "error": null
    },
    {
      "index": 35,
      "timestamp": 1760579743.1936219,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7377.373695373535,
      "response_size": 19,
      "error": null
    },
    {
      "index": 91,
      "timestamp": 1760579743.2927885,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7443.540334701538,
      "response_size": 20,
      "error": null
    },
    {
      "index": 68,
      "timestamp": 1760579743.5564194,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7719.170093536377,
      "response_size": 20,
      "error": null
    },
    {
      "index": 61,
      "timestamp": 1760579743.6013098,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7768.061876296997,
      "response_size": 20,
      "error": null
    },
    {
      "index": 17,
      "timestamp": 1760579743.6033225,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7798.073291778564,
      "response_size": 20,
      "error": null
    },
    {
      "index": 63,
      "timestamp": 1760579743.611075,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7776.827096939087,
      "response_size": 20,
      "error": null
    },
    {
      "index": 9,
      "timestamp": 1760579743.621174,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7819.918394088745,
      "response_size": 20,
      "error": null
    },
    {
      "index": 55,
      "timestamp": 1760579743.7662518,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 7937.003612518311,
      "response_size": 20,
      "error": null
    },
    {
      "index": 67,
      "timestamp": 1760579743.999791,
      "datetime": "2025-10-16 09:55:43",
      "status_code": 200,
      "latency_ms": 8163.540840148926,
      "response_size": 20,
      "error": null
    },
    {
      "index": 85,
      "timestamp": 1760579744.0067902,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8160.54105758667,
      "response_size": 20,
      "error": null
    },
    {
      "index": 100,
      "timestamp": 1760579744.0268416,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8172.5897789001465,
      "response_size": 20,
      "error": null
    },
    {
      "index": 74,
      "timestamp": 1760579744.0278468,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8187.59822845459,
      "response_size": 20,
      "error": null
    },
    {
      "index": 59,
      "timestamp": 1760579744.0623982,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8230.148077011108,
      "response_size": 20,
      "error": null
    },
    {
      "index": 65,
      "timestamp": 1760579744.2366185,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8401.370763778687,
      "response_size": 19,
      "error": null
    },
    {
      "index": 86,
      "timestamp": 1760579744.4280767,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8580.8265209198,
      "response_size": 20,
      "error": null
    },
    {
      "index": 56,
      "timestamp": 1760579744.434209,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8603.960990905762,
      "response_size": 20,
      "error": null
    },
    {
      "index": 23,
      "timestamp": 1760579744.5166273,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8707.377433776855,
      "response_size": 20,
      "error": null
    },
    {
      "index": 11,
      "timestamp": 1760579744.521632,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8719.382762908936,
      "response_size": 20,
      "error": null
    },
    {
      "index": 79,
      "timestamp": 1760579744.5326326,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8689.384698867798,
      "response_size": 20,
      "error": null
    },
    {
      "index": 69,
      "timestamp": 1760579744.7850199,
      "datetime": "2025-10-16 09:55:44",
      "status_code": 200,
      "latency_ms": 8947.770595550537,
      "response_size": 20,
      "error": null
    }
  ]
}
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544_latency_hist.png
测试组/脚本/造数脚本2/河北大学/load_test_report/压测任务_20251016_095544_rps.png
测试组/脚本/造数脚本2/河北大学/设备维保通知.py
New file
@@ -0,0 +1,112 @@
import asyncio
import aiohttp
import traceback
import time
from Util.random_util import RandomUtil  # 保证该模块中提供了生成随机数字和随机日期的函数
from Util.dingtalk_helper import DingTalkHelper
from tqdm import tqdm  # 导入进度条库
# 钉钉机器人 access_token 和 secret
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
# 请求URL和请求头
apiname = "设备维保通知"
url = "http://192.168.6.168:5537/api/notify/save"
headers = {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2MDczMTAsInVzZXJuYW1lIjoiZ2x5In0.3DC4-4oUMuIpSDT-dYn8bv9JvuPadIssUQfIKAnjAL4",
    "Content-Type": "application/json"
}
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
gender = "1"
mother_id = ""
mother_wholeCode = ""
father_id = ""
father_wholeCode = ""
algebra = 1
preCode = "性能测试"
# 生成请求体
def create_animal_data():
    random_code = RandomUtil.generate_random_number_string(0, 999999999)
    # random_date = RandomUtil.generate_random_date("2023-01-01", "2025-03-25")
    return {
        "id": "",
        "type": "6",
        "title": f"压测中{random_code}",
        "content": (
            "压测中" * 500
        ),
        "files": "/userfiles/1588133301094375425/程序附件/notify/notify/2025/10/15/173933/cs.jpg",
        "status": "1",
        "notifyRecordIds": "",
        "isNotice": "0",
        "receivingRange": "1"
    }
# 异步发送单个请求,并返回是否成功
async def create_animal(session: aiohttp.ClientSession, index: int, max_retries: int = 3):
    data = create_animal_data()
    attempt = 0
    while attempt < max_retries:
        try:
            async with session.post(url, json=data, headers=headers) as response:
                resp_text = await response.text()
                if response.status == 200:
                    print(f"[INFO] 第 {index} 个{apiname}创建成功,响应:{resp_text}")
                    return True
                else:
                    print(f"[ERROR] 第 {index} 个{apiname}创建失败,状态码:{response.status},响应:{resp_text}")
                    return False
        except Exception as e:
            attempt += 1
            status_code = getattr(e, 'status', 'N/A')
            error_trace = traceback.format_exc()
            full_log = (
                f"请求URL: {url}\n"
                f"请求头: {headers}\n"
                f"请求数据: {data}\n"
                f"状态码: {status_code}\n"
                f"异常信息: {str(e)}\n"
                f"堆栈跟踪:\n{error_trace}\n"
                f"重试次数: {attempt}/{max_retries}"
            )
            print(f"[EXCEPTION] 第 {index} 个{apiname}请求异常:\n{full_log}\n")
            if attempt < max_retries:
                # 重试前等待 1 秒
                await asyncio.sleep(1)
            else:
                return False
# 批量异步创建信息,同时显示进度条
async def batch_create_animals(batch_size: int):
    tasks = []
    # 设置超时时间为60秒(可根据需要调整)
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        for i in range(1, batch_size + 1):
            task = asyncio.ensure_future(create_animal(session, i))
            tasks.append(task)
        success_count = 0
        # 使用 asyncio.as_completed 逐个等待任务完成,并更新进度条
        with tqdm(total=batch_size, desc="创建进度") as pbar:
            for finished in asyncio.as_completed(tasks):
                result = await finished
                if result:
                    success_count += 1
                pbar.update(1)
        summary = f"\n[SUMMARY] 成功创建 {success_count}/{batch_size} 个{apiname}"
        print(summary)
        dingtalk_helper.send_message(
            f"你的批量创建{apiname}模块数据已完成:成功创建 {success_count}/{batch_size} 个{apiname},可前往系统查看!")
# 主入口
if __name__ == '__main__':
    n = 100000  # 可根据需要调整批量创建数量
    asyncio.run(batch_create_animals(n))
测试组/脚本/造数脚本2/河北大学/设备维保通知_优化.py
New file
@@ -0,0 +1,166 @@
import asyncio
import aiohttp
import traceback
import time
from Util.random_util import RandomUtil  # 你已有的工具
from Util.dingtalk_helper import DingTalkHelper
from tqdm import tqdm
import logging
# 钉钉机器人 access_token 和 secret
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
apiname = "设备维保通知"
url = "http://192.168.6.168:5537/api/notify/save"
headers = {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2MDczMTAsInVzZXJuYW1lIjoiZ2x5In0.3DC4-4oUMuIpSDT-dYn8bv9JvuPadIssUQfIKAnjAL4",
    "Content-Type": "application/json"
}
# 并发 worker 数(控制并发请求数),根据本机性能与被测服务能力调整,例如 50、100
NUM_WORKERS = 100
# 总请求数(不要一开始就设过高)
TOTAL_REQUESTS = 2000000
# 每个请求最大重试次数
MAX_RETRIES = 3
# aiohttp 连接/超时设置
REQUEST_TIMEOUT = 60  # 秒
# ------------------------
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
# 预生成不变的大 body 字段,减少每次构造开销
LARGE_CONTENT = "压测中" * 500
FILES_PATH = "/userfiles/1588133301094375425/程序附件/notify/notify/2025/10/15/173933/cs.jpg"
def create_animal_data(idx: int):
    random_code = RandomUtil.generate_random_number_string(0, 999999999)
    return {
        "id": "",
        "type": "6",
        "title": f"压测中{random_code}",
        "content": LARGE_CONTENT,
        "files": FILES_PATH,
        "status": "1",
        "notifyRecordIds": "",
        "isNotice": "0",
        "receivingRange": "1"
    }
async def create_animal(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES):
    """
    单个请求,包含重试。返回 (success: bool, index: int, errmsg: str|None)
    """
    attempt = 0
    while attempt < max_retries:
        data = create_animal_data(index)
        try:
            async with session.post(url, json=data, headers=headers) as resp:
                text = await resp.text()
                if resp.status == 200:
                    # 成功不频繁打印,交给外层进度统计
                    return True, index, None
                else:
                    # 非 200 当成失败(可自定义对特定状态的判定)
                    errmsg = f"状态码 {resp.status}, body: {text}"
                    attempt += 1
                    # 指数退避,避免瞬时打爆目标
                    await asyncio.sleep(min(10, 2 ** attempt))
        except Exception as e:
            # 记录简短异常信息(避免每次打印超长堆栈)
            err_msg = f"{type(e).__name__}: {str(e)}"
            attempt += 1
            await asyncio.sleep(min(10, 2 ** attempt))
    # 走到这里表示重试失败
    return False, index, f"重试 {max_retries} 次后失败"
async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession,
                 pbar: tqdm, success_counter: dict, failed_list: list, lock: asyncio.Lock):
    """
    worker 从队列获取任务并执行 create_animal
    """
    while True:
        idx = await queue.get()
        if idx is None:
            queue.task_done()
            break
        try:
            ok, index, errmsg = await create_animal(session, idx)
            async with lock:
                if ok:
                    success_counter["count"] += 1
                else:
                    failed_list.append((index, errmsg))
                pbar.update(1)
        except Exception as e:
            # 捕获意外错误,记录并继续
            async with lock:
                failed_list.append((idx, f"Worker异常: {type(e).__name__}:{e}"))
                pbar.update(1)
        finally:
            queue.task_done()
async def batch_create_animals(total: int, num_workers: int):
    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
    connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False)
    # 减少session请求头每次设置的开销,也可在 post 中单独传 headers
    async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
        queue = asyncio.Queue()
        for i in range(1, total + 1):
            await queue.put(i)
        # 结束信号
        for _ in range(num_workers):
            await queue.put(None)
        success_counter = {"count": 0}
        failed_list = []
        lock = asyncio.Lock()
        # tqdm 在主线程创建
        with tqdm(total=total, desc="创建进度") as pbar:
            workers = [
                asyncio.create_task(worker(i, queue, session, pbar, success_counter, failed_list, lock))
                for i in range(num_workers)
            ]
            # 等待所有 worker 完成
            await asyncio.gather(*workers)
        # 汇总
        total_success = success_counter["count"]
        total_failed = len(failed_list)
        summary = f"\n[SUMMARY] 成功创建 {total_success}/{total} 个 {apiname},失败 {total_failed} 个"
        print(summary)
        # 可只发送简短信息到钉钉(避免太长)
        dingtalk_helper.send_message(
            f"你的批量创建{apiname}模块数据已完成:成功创建 {total_success}/{total} 个,失败 {total_failed} 个(详见日志)。"
        )
        # 如果需要打印失败详情,这里只打印前 50 条,避免控制台炸屏
        if total_failed:
            print("失败示例(最多显示50条):")
            for idx, err in failed_list[:50]:
                print(f"  #{idx} => {err}")
if __name__ == '__main__':
    # 运行前建议:
    # 1) 先用小的总数(例如 100 或 1000)跑通;
    # 2) 观察目标服务器与本机负载,再把 TOTAL_REQUESTS 调高;
    # 3) 调整 NUM_WORKERS 与 REQUEST_TIMEOUT 以获得最佳吞吐/稳定性。
    NUM_WORKERS = NUM_WORKERS
    TOTAL_REQUESTS = TOTAL_REQUESTS
    # 简单日志设置(只打印 WARNING 及以上,避免太多日志)
    logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s %(message)s')
    asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS))
测试组/脚本/造数脚本2/河北大学/设备维保通知_压测.py
New file
@@ -0,0 +1,221 @@
"""
集成压测脚本(带压测报告生成并通过钉钉发送摘要)
说明:
 - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。
 - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。
 - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。
 - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选)
 - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。
"""
import sys
import os
# 将上一级目录加入模块搜索路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio
import aiohttp
import time
import traceback
import datetime
from tqdm import tqdm
from Util.random_util import RandomUtil
from Util.dingtalk_helper import DingTalkHelper
# --- 配置 ---
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
apiname = "设备维保通知"
url = "http://192.168.6.168:5537/api/notify/save"
headers = {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjA2MDczMTAsInVzZXJuYW1lIjoiZ2x5In0.3DC4-4oUMuIpSDT-dYn8bv9JvuPadIssUQfIKAnjAL4",
    "Content-Type": "application/json"
}
NUM_WORKERS = 100
TOTAL_REQUESTS = 2000000
MAX_RETRIES = 3
REQUEST_TIMEOUT = 60
OUTPUT_DIR = './load_test_report'
# --- 初始化 ---
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
LARGE_CONTENT = "压测中" * 500
FILES_PATH = "/userfiles/1588133301094375425/程序附件/notify/notify/2025/10/15/173933/cs.jpg"
def create_animal_data(idx: int):
    random_code = RandomUtil.generate_random_number_string(0, 999999999)
    return {
        "id": "",
        "type": "6",
        "title": f"压测中{random_code}",
        "content": LARGE_CONTENT,
        "files": FILES_PATH,
        "status": "1",
        "notifyRecordIds": "",
        "isNotice": "0",
        "receivingRange": "1"
    }
async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES):
    attempt = 0
    last_err = None
    while attempt < max_retries:
        data = create_animal_data(index)
        start = time.time()
        try:
            async with session.post(url, json=data, headers=headers) as resp:
                text = await resp.text()
                latency_ms = (time.time() - start) * 1000.0
                status = resp.status
                if status == 200:
                    return {
                        'index': index,
                        'timestamp': time.time(),
                        'status_code': status,
                        'latency_ms': latency_ms,
                        'response_size': len(text) if text is not None else None,
                        'error': None
                    }
                else:
                    last_err = f'status_{status}:{text}'
                    attempt += 1
                    await asyncio.sleep(min(10, 2 ** attempt))
        except Exception as e:
            latency_ms = (time.time() - start) * 1000.0
            last_err = f'{type(e).__name__}:{str(e)}'
            attempt += 1
            await asyncio.sleep(min(10, 2 ** attempt))
    # 最终失败
    return {
        'index': index,
        'timestamp': time.time(),
        'status_code': 0,
        'latency_ms': latency_ms if 'latency_ms' in locals() else 0,
        'response_size': None,
        'error': last_err
    }
async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock):
    while True:
        idx = await queue.get()
        if idx is None:
            queue.task_done()
            break
        try:
            res = await perform_request(session, idx)
            # 记录到报告生成器
            gen.record_result(
                index=res['index'],
                timestamp=res['timestamp'],
                status_code=int(res['status_code']),
                latency_ms=float(res['latency_ms']),
                response_size=res.get('response_size'),
                error=res.get('error')
            )
            async with lock:
                if res['status_code'] and 200 <= res['status_code'] < 300:
                    success_counter['count'] += 1
                else:
                    failed_list.append((res['index'], res.get('error')))
                pbar.update(1)
        except Exception as e:
            async with lock:
                failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}'))
                pbar.update(1)
        finally:
            queue.task_done()
async def batch_create_animals(total: int, num_workers: int):
    # 动态加载报告生成器模块(支持中文文件名)
    gen = None
    try:
        import importlib.util
        script_dir = os.path.dirname(os.path.abspath(__file__))
        report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py')
        if os.path.exists(report_path):
            spec = importlib.util.spec_from_file_location('report_module', report_path)
            report_module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(report_module)
            LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator')
        else:
            # 备用:尝试直接导入模块名(若你的文件名已改为 ascii)
            from report_generator import LoadTestReportGenerator  # type: ignore
        gen = LoadTestReportGenerator(test_name='压测任务', report_title='压测详细报告')
    except Exception as e:
        print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e)
        raise
    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
    connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False)
    async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
        queue = asyncio.Queue()
        for i in range(1, total + 1):
            await queue.put(i)
        for _ in range(num_workers):
            await queue.put(None)
        success_counter = {'count': 0}
        failed_list = []
        lock = asyncio.Lock()
        with tqdm(total=total, desc='创建进度') as pbar:
            workers = [
                asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock))
                for i in range(num_workers)
            ]
            await asyncio.gather(*workers)
        # 任务完成,生成报告
        os.makedirs(OUTPUT_DIR, exist_ok=True)
        outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx'])
        stats = gen.compute_stats()
        # 构造钉钉摘要消息(中文)
        now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}']
        msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}")
        msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}")
        lat = stats.get('latency_ms', {})
        msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}")
        # 列出生成的报告文件
        file_list = []
        for k, v in outputs.items():
            if k == 'charts':
                for cname, cpath in v.items():
                    file_list.append(os.path.abspath(cpath))
            else:
                file_list.append(os.path.abspath(v))
        msg.append('生成文件:')
        for p in file_list:
            msg.append(p)
        final_msg = '\n'.join(msg)
        # 发送钉钉消息
        try:
            dingtalk_helper.send_message(final_msg)
        except Exception as e:
            print('发送钉钉消息失败:', e)
        print('\n[SUMMARY] 已生成报告并发送钉钉摘要。')
        print('成功数:', success_counter['count'], ' 失败数:', len(failed_list))
        if failed_list:
            print('失败示例(最多显示50条):')
            for idx, err in failed_list[:50]:
                print(f'  #{idx} => {err}')
if __name__ == '__main__':
    # 运行前建议先用小规模测试
    TOTAL_REQUESTS = TOTAL_REQUESTS
    NUM_WORKERS = NUM_WORKERS
    asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS))
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917.details.csv
New file
Diff too large
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917.docx
Binary files differ
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917.html
New file
@@ -0,0 +1,50 @@
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>压测详细报告</title>
          <style>
            body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
            h2{color:#2c3e50}
            table{border-collapse:collapse; width:100%}
            th,td{padding:6px; text-align:left}
          </style>
        </head>
        <body>
          <h1>压测详细报告</h1>
        <h2>摘要</h2>
        <ul>
          <li>报告名称:压测详细报告</li>
          <li>生成时间:2025-11-12 16:49:19</li>
          <li>总请求数:10000</li>
          <li>成功数:10000,失败数:0,成功率:100.00%</li>
          <li>总耗时(秒):493.44</li>
          <li>平均吞吐(req/s):20.27</li>
        </ul>
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:2995.07</li>
          <li>最大:8979.18</li>
          <li>平均:4966.85</li>
          <li>中位数(P50):4861.56</li>
          <li>P90:5892.74,P95:6121.15,P99:7008.39</li>
        </ul>
          <h2>状态码分布</h2><ul><li>200: 10000</li></ul>
          <h2>错误汇总</h2><p>无错误记录</p>
          <h2>图表</h2><div><h3>latency_hist</h3><img src="动物房管理新建动物房压测任务_20251112_164917_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="动物房管理新建动物房压测任务_20251112_164917_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          <tr><td>27</td><td>2025-11-12 16:41:04</td><td>200</td><td>4316.097021102905</td><td>12</td><td></td></tr><tr><td>17</td><td>2025-11-12 16:41:04</td><td>200</td><td>4503.03053855896</td><td>12</td><td></td></tr><tr><td>5</td><td>2025-11-12 16:41:04</td><td>200</td><td>4509.0272426605225</td><td>12</td><td></td></tr><tr><td>2</td><td>2025-11-12 16:41:04</td><td>200</td><td>4527.026891708374</td><td>12</td><td></td></tr><tr><td>1</td><td>2025-11-12 16:41:04</td><td>200</td><td>4531.03494644165</td><td>12</td><td></td></tr><tr><td>3</td><td>2025-11-12 16:41:04</td><td>200</td><td>4544.030666351318</td><td>12</td><td></td></tr><tr><td>11</td><td>2025-11-12 16:41:04</td><td>200</td><td>4560.0340366363525</td><td>12</td><td></td></tr><tr><td>13</td><td>2025-11-12 16:41:04</td><td>200</td><td>4577.026128768921</td><td>12</td><td></td></tr><tr><td>33</td><td>2025-11-12 16:41:04</td><td>200</td><td>4572.026252746582</td><td>12</td><td></td></tr><tr><td>4</td><td>2025-11-12 16:41:04</td><td>200</td><td>4583.027601242065</td><td>12</td><td></td></tr><tr><td>32</td><td>2025-11-12 16:41:04</td><td>200</td><td>4583.027601242065</td><td>12</td><td></td></tr><tr><td>22</td><td>2025-11-12 16:41:04</td><td>200</td><td>4594.027280807495</td><td>12</td><td></td></tr><tr><td>10</td><td>2025-11-12 16:41:04</td><td>200</td><td>4599.024772644043</td><td>12</td><td></td></tr><tr><td>21</td><td>2025-11-12 16:41:04</td><td>200</td><td>4595.026016235352</td><td>12</td><td></td></tr><tr><td>36</td><td>2025-11-12 16:41:04</td><td>200</td><td>4610.026121139526</td><td>12</td><td></td></tr><tr><td>15</td><td>2025-11-12 16:41:04</td><td>200</td><td>4618.0260181427</td><td>12</td><td></td></tr><tr><td>9</td><td>2025-11-12 16:41:04</td><td>200</td><td>4621.026039123535</td><td>12</td><td></td></tr><tr><td>14</td><td>2025-11-12 16:41:04</td><td>200</td><td>4631.024599075317</td><td>12</td><td></td></tr><tr><td>20</td><td>2025-11-12 16:41:04</td><td>200</td><td>4631.0272216796875</td><td>12</td><td></td></tr><tr><td>12</td><td>2025-11-12 16:41:04</td><td>200</td><td>4634.026288986206</td><td>12</td><td></td></tr><tr><td>49</td><td>2025-11-12 16:41:04</td><td>200</td><td>4622.025966644287</td><td>12</td><td></td></tr><tr><td>38</td><td>2025-11-12 16:41:04</td><td>200</td><td>4627.026319503784</td><td>12</td><td></td></tr><tr><td>24</td><td>2025-11-12 16:41:04</td><td>200</td><td>4633.0246925354</td><td>12</td><td></td></tr><tr><td>69</td><td>2025-11-12 16:41:04</td><td>200</td><td>4627.023458480835</td><td>12</td><td></td></tr><tr><td>19</td><td>2025-11-12 16:41:04</td><td>200</td><td>4649.024724960327</td><td>12</td><td></td></tr><tr><td>79</td><td>2025-11-12 16:41:04</td><td>200</td><td>4632.027387619019</td><td>12</td><td></td></tr><tr><td>7</td><td>2025-11-12 16:41:04</td><td>200</td><td>4659.02853012085</td><td>12</td><td></td></tr><tr><td>6</td><td>2025-11-12 16:41:04</td><td>200</td><td>4660.027503967285</td><td>12</td><td></td></tr><tr><td>47</td><td>2025-11-12 16:41:04</td><td>200</td><td>4647.025108337402</td><td>12</td><td></td></tr><tr><td>25</td><td>2025-11-12 16:41:04</td><td>200</td><td>4673.023462295532</td><td>12</td><td></td></tr><tr><td>81</td><td>2025-11-12 16:41:04</td><td>200</td><td>4654.026746749878</td><td>12</td><td></td></tr><tr><td>8</td><td>2025-11-12 16:41:04</td><td>200</td><td>4681.026220321655</td><td>12</td><td></td></tr><tr><td>18</td><td>2025-11-12 16:41:04</td><td>200</td><td>4678.02619934082</td><td>12</td><td></td></tr><tr><td>16</td><td>2025-11-12 16:41:04</td><td>200</td><td>4683.026075363159</td><td>12</td><td></td></tr><tr><td>34</td><td>2025-11-12 16:41:04</td><td>200</td><td>4680.025339126587</td><td>12</td><td></td></tr><tr><td>77</td><td>2025-11-12 16:41:04</td><td>200</td><td>4667.027473449707</td><td>12</td><td></td></tr><tr><td>63</td><td>2025-11-12 16:41:04</td><td>200</td><td>4682.023763656616</td><td>12</td><td></td></tr><tr><td>70</td><td>2025-11-12 16:41:04</td><td>200</td><td>4683.023452758789</td><td>12</td><td></td></tr><tr><td>67</td><td>2025-11-12 16:41:04</td><td>200</td><td>4687.023401260376</td><td>12</td><td></td></tr><tr><td>23</td><td>2025-11-12 16:41:04</td><td>200</td><td>4703.025102615356</td><td>12</td><td></td></tr><tr><td>92</td><td>2025-11-12 16:41:04</td><td>200</td><td>4689.024209976196</td><td>12</td><td></td></tr><tr><td>87</td><td>2025-11-12 16:41:04</td><td>200</td><td>4696.024417877197</td><td>12</td><td></td></tr><tr><td>56</td><td>2025-11-12 16:41:04</td><td>200</td><td>4708.02640914917</td><td>12</td><td></td></tr><tr><td>65</td><td>2025-11-12 16:41:04</td><td>200</td><td>4705.023765563965</td><td>12</td><td></td></tr><tr><td>68</td><td>2025-11-12 16:41:04</td><td>200</td><td>4705.024003982544</td><td>12</td><td></td></tr><tr><td>43</td><td>2025-11-12 16:41:04</td><td>200</td><td>4729.025363922119</td><td>12</td><td></td></tr><tr><td>64</td><td>2025-11-12 16:41:04</td><td>200</td><td>4725.023984909058</td><td>12</td><td></td></tr><tr><td>85</td><td>2025-11-12 16:41:04</td><td>200</td><td>4727.025270462036</td><td>12</td><td></td></tr><tr><td>58</td><td>2025-11-12 16:41:04</td><td>200</td><td>4738.027095794678</td><td>12</td><td></td></tr><tr><td>90</td><td>2025-11-12 16:41:04</td><td>200</td><td>4748.024463653564</td><td>12</td><td></td></tr><tr><td>28</td><td>2025-11-12 16:41:04</td><td>200</td><td>4771.021842956543</td><td>12</td><td></td></tr><tr><td>46</td><td>2025-11-12 16:41:04</td><td>200</td><td>4764.024019241333</td><td>12</td><td></td></tr><tr><td>26</td><td>2025-11-12 16:41:04</td><td>200</td><td>4787.028789520264</td><td>12</td><td></td></tr><tr><td>88</td><td>2025-11-12 16:41:04</td><td>200</td><td>4770.025014877319</td><td>12</td><td></td></tr><tr><td>35</td><td>2025-11-12 16:41:04</td><td>200</td><td>4790.027618408203</td><td>12</td><td></td></tr><tr><td>82</td><td>2025-11-12 16:41:04</td><td>200</td><td>4774.026393890381</td><td>12</td><td></td></tr><tr><td>50</td><td>2025-11-12 16:41:04</td><td>200</td><td>4788.024663925171</td><td>12</td><td></td></tr><tr><td>31</td><td>2025-11-12 16:41:04</td><td>200</td><td>4805.024862289429</td><td>12</td><td></td></tr><tr><td>73</td><td>2025-11-12 16:41:04</td><td>200</td><td>4792.027235031128</td><td>12</td><td></td></tr><tr><td>80</td><td>2025-11-12 16:41:04</td><td>200</td><td>4795.027494430542</td><td>12</td><td></td></tr><tr><td>94</td><td>2025-11-12 16:41:04</td><td>200</td><td>4790.027618408203</td><td>12</td><td></td></tr><tr><td>44</td><td>2025-11-12 16:41:04</td><td>200</td><td>4809.029340744019</td><td>12</td><td></td></tr><tr><td>48</td><td>2025-11-12 16:41:04</td><td>200</td><td>4815.025806427002</td><td>12</td><td></td></tr><tr><td>71</td><td>2025-11-12 16:41:04</td><td>200</td><td>4809.027433395386</td><td>12</td><td></td></tr><tr><td>72</td><td>2025-11-12 16:41:04</td><td>200</td><td>4810.025930404663</td><td>12</td><td></td></tr><tr><td>42</td><td>2025-11-12 16:41:04</td><td>200</td><td>4829.024314880371</td><td>12</td><td></td></tr><tr><td>78</td><td>2025-11-12 16:41:04</td><td>200</td><td>4818.0272579193115</td><td>12</td><td></td></tr><tr><td>57</td><td>2025-11-12 16:41:04</td><td>200</td><td>4826.025724411011</td><td>12</td><td></td></tr><tr><td>61</td><td>2025-11-12 16:41:04</td><td>200</td><td>4828.025341033936</td><td>12</td><td></td></tr><tr><td>41</td><td>2025-11-12 16:41:04</td><td>200</td><td>4837.023496627808</td><td>12</td><td></td></tr><tr><td>52</td><td>2025-11-12 16:41:04</td><td>200</td><td>4845.023393630981</td><td>12</td><td></td></tr><tr><td>29</td><td>2025-11-12 16:41:04</td><td>200</td><td>4854.026794433594</td><td>12</td><td></td></tr><tr><td>30</td><td>2025-11-12 16:41:04</td><td>200</td><td>4857.025146484375</td><td>12</td><td></td></tr><tr><td>84</td><td>2025-11-12 16:41:04</td><td>200</td><td>4839.025497436523</td><td>12</td><td></td></tr><tr><td>37</td><td>2025-11-12 16:41:04</td><td>200</td><td>4856.026649475098</td><td>12</td><td></td></tr><tr><td>59</td><td>2025-11-12 16:41:04</td><td>200</td><td>4863.027334213257</td><td>12</td><td></td></tr><tr><td>60</td><td>2025-11-12 16:41:04</td><td>200</td><td>4864.0265464782715</td><td>12</td><td></td></tr><tr><td>93</td><td>2025-11-12 16:41:04</td><td>200</td><td>4853.025436401367</td><td>12</td><td></td></tr><tr><td>54</td><td>2025-11-12 16:41:04</td><td>200</td><td>4870.025157928467</td><td>12</td><td></td></tr><tr><td>75</td><td>2025-11-12 16:41:04</td><td>200</td><td>4864.025831222534</td><td>12</td><td></td></tr><tr><td>89</td><td>2025-11-12 16:41:04</td><td>200</td><td>4860.024690628052</td><td>12</td><td></td></tr><tr><td>97</td><td>2025-11-12 16:41:04</td><td>200</td><td>4859.026193618774</td><td>12</td><td></td></tr><tr><td>86</td><td>2025-11-12 16:41:05</td><td>200</td><td>4870.026350021362</td><td>12</td><td></td></tr><tr><td>96</td><td>2025-11-12 16:41:05</td><td>200</td><td>4868.025541305542</td><td>12</td><td></td></tr><tr><td>53</td><td>2025-11-12 16:41:05</td><td>200</td><td>4886.023998260498</td><td>12</td><td></td></tr><tr><td>98</td><td>2025-11-12 16:41:05</td><td>200</td><td>4877.027273178101</td><td>12</td><td></td></tr><tr><td>51</td><td>2025-11-12 16:41:05</td><td>200</td><td>4895.024061203003</td><td>12</td><td></td></tr><tr><td>74</td><td>2025-11-12 16:41:05</td><td>200</td><td>4888.025999069214</td><td>12</td><td></td></tr><tr><td>100</td><td>2025-11-12 16:41:05</td><td>200</td><td>4882.0250034332275</td><td>12</td><td></td></tr><tr><td>99</td><td>2025-11-12 16:41:05</td><td>200</td><td>4882.0250034332275</td><td>12</td><td></td></tr><tr><td>39</td><td>2025-11-12 16:41:05</td><td>200</td><td>4905.02667427063</td><td>12</td><td></td></tr><tr><td>55</td><td>2025-11-12 16:41:05</td><td>200</td><td>4910.027503967285</td><td>12</td><td></td></tr><tr><td>91</td><td>2025-11-12 16:41:05</td><td>200</td><td>4905.026197433472</td><td>12</td><td></td></tr><tr><td>62</td><td>2025-11-12 16:41:05</td><td>200</td><td>4927.027463912964</td><td>12</td><td></td></tr><tr><td>83</td><td>2025-11-12 16:41:05</td><td>200</td><td>4921.027183532715</td><td>12</td><td></td></tr><tr><td>76</td><td>2025-11-12 16:41:05</td><td>200</td><td>4948.025703430176</td><td>12</td><td></td></tr><tr><td>45</td><td>2025-11-12 16:41:05</td><td>200</td><td>4999.026536941528</td><td>12</td><td></td></tr><tr><td>95</td><td>2025-11-12 16:41:05</td><td>200</td><td>5127.027750015259</td><td>12</td><td></td></tr><tr><td>66</td><td>2025-11-12 16:41:05</td><td>200</td><td>5412.693023681641</td><td>12</td><td></td></tr><tr><td>40</td><td>2025-11-12 16:41:05</td><td>200</td><td>5575.217962265015</td><td>12</td><td></td></tr>
        </table>
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917.summary.json
New file
Diff too large
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917_latency_hist.png
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_164917_rps.png
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052.details.csv
New file
Diff too large
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052.docx
Binary files differ
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052.html
New file
@@ -0,0 +1,50 @@
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>压测详细报告</title>
          <style>
            body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
            h2{color:#2c3e50}
            table{border-collapse:collapse; width:100%}
            th,td{padding:6px; text-align:left}
          </style>
        </head>
        <body>
          <h1>压测详细报告</h1>
        <h2>摘要</h2>
        <ul>
          <li>报告名称:压测详细报告</li>
          <li>生成时间:2025-11-12 17:50:53</li>
          <li>总请求数:10000</li>
          <li>成功数:10000,失败数:0,成功率:100.00%</li>
          <li>总耗时(秒):525.46</li>
          <li>平均吞吐(req/s):19.03</li>
        </ul>
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:2581.28</li>
          <li>最大:11187.67</li>
          <li>平均:5282.15</li>
          <li>中位数(P50):5057.26</li>
          <li>P90:6251.82,P95:6611.83,P99:8029.87</li>
        </ul>
          <h2>状态码分布</h2><ul><li>200: 10000</li></ul>
          <h2>错误汇总</h2><p>无错误记录</p>
          <h2>图表</h2><div><h3>latency_hist</h3><img src="动物房管理新建动物房压测任务_20251112_175052_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="动物房管理新建动物房压测任务_20251112_175052_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          <tr><td>8</td><td>2025-11-12 17:42:06</td><td>200</td><td>4016.1163806915283</td><td>12</td><td></td></tr><tr><td>11</td><td>2025-11-12 17:42:06</td><td>200</td><td>4239.122629165649</td><td>12</td><td></td></tr><tr><td>6</td><td>2025-11-12 17:42:06</td><td>200</td><td>4252.122640609741</td><td>12</td><td></td></tr><tr><td>2</td><td>2025-11-12 17:42:06</td><td>200</td><td>4258.126497268677</td><td>12</td><td></td></tr><tr><td>9</td><td>2025-11-12 17:42:06</td><td>200</td><td>4266.122341156006</td><td>12</td><td></td></tr><tr><td>10</td><td>2025-11-12 17:42:06</td><td>200</td><td>4271.126985549927</td><td>12</td><td></td></tr><tr><td>7</td><td>2025-11-12 17:42:06</td><td>200</td><td>4277.122020721436</td><td>12</td><td></td></tr><tr><td>1</td><td>2025-11-12 17:42:06</td><td>200</td><td>4286.126136779785</td><td>12</td><td></td></tr><tr><td>4</td><td>2025-11-12 17:42:06</td><td>200</td><td>4299.125909805298</td><td>12</td><td></td></tr><tr><td>30</td><td>2025-11-12 17:42:06</td><td>200</td><td>4308.122396469116</td><td>12</td><td></td></tr><tr><td>5</td><td>2025-11-12 17:42:06</td><td>200</td><td>4318.123817443848</td><td>12</td><td></td></tr><tr><td>33</td><td>2025-11-12 17:42:06</td><td>200</td><td>4313.12108039856</td><td>12</td><td></td></tr><tr><td>13</td><td>2025-11-12 17:42:06</td><td>200</td><td>4326.1213302612305</td><td>12</td><td></td></tr><tr><td>12</td><td>2025-11-12 17:42:06</td><td>200</td><td>4332.123517990112</td><td>12</td><td></td></tr><tr><td>31</td><td>2025-11-12 17:42:06</td><td>200</td><td>4332.1213722229</td><td>12</td><td></td></tr><tr><td>34</td><td>2025-11-12 17:42:06</td><td>200</td><td>4332.123041152954</td><td>12</td><td></td></tr><tr><td>15</td><td>2025-11-12 17:42:06</td><td>200</td><td>4342.123031616211</td><td>12</td><td></td></tr><tr><td>24</td><td>2025-11-12 17:42:06</td><td>200</td><td>4340.123414993286</td><td>12</td><td></td></tr><tr><td>21</td><td>2025-11-12 17:42:06</td><td>200</td><td>4356.121778488159</td><td>12</td><td></td></tr><tr><td>26</td><td>2025-11-12 17:42:06</td><td>200</td><td>4355.121612548828</td><td>12</td><td></td></tr><tr><td>32</td><td>2025-11-12 17:42:06</td><td>200</td><td>4354.120969772339</td><td>12</td><td></td></tr><tr><td>17</td><td>2025-11-12 17:42:06</td><td>200</td><td>4361.12117767334</td><td>12</td><td></td></tr><tr><td>27</td><td>2025-11-12 17:42:06</td><td>200</td><td>4360.122919082642</td><td>12</td><td></td></tr><tr><td>19</td><td>2025-11-12 17:42:06</td><td>200</td><td>4369.12202835083</td><td>12</td><td></td></tr><tr><td>14</td><td>2025-11-12 17:42:06</td><td>200</td><td>4378.149032592773</td><td>12</td><td></td></tr><tr><td>16</td><td>2025-11-12 17:42:06</td><td>200</td><td>4383.121728897095</td><td>12</td><td></td></tr><tr><td>29</td><td>2025-11-12 17:42:06</td><td>200</td><td>4384.122133255005</td><td>12</td><td></td></tr><tr><td>22</td><td>2025-11-12 17:42:06</td><td>200</td><td>4388.124227523804</td><td>12</td><td></td></tr><tr><td>25</td><td>2025-11-12 17:42:06</td><td>200</td><td>4389.121770858765</td><td>12</td><td></td></tr><tr><td>56</td><td>2025-11-12 17:42:06</td><td>200</td><td>4406.123161315918</td><td>12</td><td></td></tr><tr><td>47</td><td>2025-11-12 17:42:06</td><td>200</td><td>4410.123348236084</td><td>12</td><td></td></tr><tr><td>40</td><td>2025-11-12 17:42:06</td><td>200</td><td>4413.124084472656</td><td>12</td><td></td></tr><tr><td>63</td><td>2025-11-12 17:42:06</td><td>200</td><td>4406.125783920288</td><td>12</td><td></td></tr><tr><td>51</td><td>2025-11-12 17:42:06</td><td>200</td><td>4410.128593444824</td><td>12</td><td></td></tr><tr><td>3</td><td>2025-11-12 17:42:06</td><td>200</td><td>4439.1210079193115</td><td>12</td><td></td></tr><tr><td>37</td><td>2025-11-12 17:42:06</td><td>200</td><td>4427.123308181763</td><td>12</td><td></td></tr><tr><td>20</td><td>2025-11-12 17:42:06</td><td>200</td><td>4436.121940612793</td><td>12</td><td></td></tr><tr><td>89</td><td>2025-11-12 17:42:06</td><td>200</td><td>4416.125774383545</td><td>12</td><td></td></tr><tr><td>64</td><td>2025-11-12 17:42:06</td><td>200</td><td>4426.120758056641</td><td>12</td><td></td></tr><tr><td>65</td><td>2025-11-12 17:42:06</td><td>200</td><td>4426.12361907959</td><td>12</td><td></td></tr><tr><td>28</td><td>2025-11-12 17:42:06</td><td>200</td><td>4445.1234340667725</td><td>12</td><td></td></tr><tr><td>59</td><td>2025-11-12 17:42:06</td><td>200</td><td>4435.127735137939</td><td>12</td><td></td></tr><tr><td>38</td><td>2025-11-12 17:42:06</td><td>200</td><td>4445.130348205566</td><td>12</td><td></td></tr><tr><td>81</td><td>2025-11-12 17:42:06</td><td>200</td><td>4440.12451171875</td><td>12</td><td></td></tr><tr><td>100</td><td>2025-11-12 17:42:06</td><td>200</td><td>4434.125185012817</td><td>12</td><td></td></tr><tr><td>43</td><td>2025-11-12 17:42:06</td><td>200</td><td>4456.123113632202</td><td>12</td><td></td></tr><tr><td>73</td><td>2025-11-12 17:42:06</td><td>200</td><td>4446.1236000061035</td><td>12</td><td></td></tr><tr><td>61</td><td>2025-11-12 17:42:06</td><td>200</td><td>4452.127695083618</td><td>12</td><td></td></tr><tr><td>54</td><td>2025-11-12 17:42:06</td><td>200</td><td>4455.12580871582</td><td>12</td><td></td></tr><tr><td>71</td><td>2025-11-12 17:42:06</td><td>200</td><td>4450.122833251953</td><td>12</td><td></td></tr><tr><td>74</td><td>2025-11-12 17:42:06</td><td>200</td><td>4458.124399185181</td><td>12</td><td></td></tr><tr><td>50</td><td>2025-11-12 17:42:06</td><td>200</td><td>4468.123197555542</td><td>12</td><td></td></tr><tr><td>69</td><td>2025-11-12 17:42:06</td><td>200</td><td>4462.125301361084</td><td>12</td><td></td></tr><tr><td>58</td><td>2025-11-12 17:42:06</td><td>200</td><td>4467.124700546265</td><td>12</td><td></td></tr><tr><td>49</td><td>2025-11-12 17:42:06</td><td>200</td><td>4473.12068939209</td><td>12</td><td></td></tr><tr><td>87</td><td>2025-11-12 17:42:06</td><td>200</td><td>4460.125207901001</td><td>12</td><td></td></tr><tr><td>53</td><td>2025-11-12 17:42:06</td><td>200</td><td>4481.122970581055</td><td>12</td><td></td></tr><tr><td>41</td><td>2025-11-12 17:42:06</td><td>200</td><td>4486.126184463501</td><td>12</td><td></td></tr><tr><td>48</td><td>2025-11-12 17:42:06</td><td>200</td><td>4486.12117767334</td><td>12</td><td></td></tr><tr><td>46</td><td>2025-11-12 17:42:06</td><td>200</td><td>4491.122245788574</td><td>12</td><td></td></tr><tr><td>52</td><td>2025-11-12 17:42:06</td><td>200</td><td>4494.122743606567</td><td>12</td><td></td></tr><tr><td>60</td><td>2025-11-12 17:42:06</td><td>200</td><td>4492.126941680908</td><td>12</td><td></td></tr><tr><td>57</td><td>2025-11-12 17:42:06</td><td>200</td><td>4494.1229820251465</td><td>12</td><td></td></tr><tr><td>55</td><td>2025-11-12 17:42:06</td><td>200</td><td>4496.124744415283</td><td>12</td><td></td></tr><tr><td>42</td><td>2025-11-12 17:42:06</td><td>200</td><td>4509.125709533691</td><td>12</td><td></td></tr><tr><td>92</td><td>2025-11-12 17:42:07</td><td>200</td><td>4492.124795913696</td><td>12</td><td></td></tr><tr><td>95</td><td>2025-11-12 17:42:07</td><td>200</td><td>4493.124961853027</td><td>12</td><td></td></tr><tr><td>39</td><td>2025-11-12 17:42:07</td><td>200</td><td>4516.12401008606</td><td>12</td><td></td></tr><tr><td>98</td><td>2025-11-12 17:42:07</td><td>200</td><td>4495.122909545898</td><td>12</td><td></td></tr><tr><td>86</td><td>2025-11-12 17:42:07</td><td>200</td><td>4503.12352180481</td><td>12</td><td></td></tr><tr><td>84</td><td>2025-11-12 17:42:07</td><td>200</td><td>4504.121541976929</td><td>12</td><td></td></tr><tr><td>78</td><td>2025-11-12 17:42:07</td><td>200</td><td>4513.124465942383</td><td>12</td><td></td></tr><tr><td>62</td><td>2025-11-12 17:42:07</td><td>200</td><td>4521.124839782715</td><td>12</td><td></td></tr><tr><td>80</td><td>2025-11-12 17:42:07</td><td>200</td><td>4516.124248504639</td><td>12</td><td></td></tr><tr><td>76</td><td>2025-11-12 17:42:07</td><td>200</td><td>4517.125606536865</td><td>12</td><td></td></tr><tr><td>44</td><td>2025-11-12 17:42:07</td><td>200</td><td>4532.1221351623535</td><td>12</td><td></td></tr><tr><td>91</td><td>2025-11-12 17:42:07</td><td>200</td><td>4523.122787475586</td><td>12</td><td></td></tr><tr><td>82</td><td>2025-11-12 17:42:07</td><td>200</td><td>4527.12345123291</td><td>12</td><td></td></tr><tr><td>68</td><td>2025-11-12 17:42:07</td><td>200</td><td>4535.124063491821</td><td>12</td><td></td></tr><tr><td>77</td><td>2025-11-12 17:42:07</td><td>200</td><td>4533.736705780029</td><td>12</td><td></td></tr><tr><td>75</td><td>2025-11-12 17:42:07</td><td>200</td><td>4536.733150482178</td><td>12</td><td></td></tr><tr><td>85</td><td>2025-11-12 17:42:07</td><td>200</td><td>4539.735317230225</td><td>12</td><td></td></tr><tr><td>36</td><td>2025-11-12 17:42:07</td><td>200</td><td>4562.7336502075195</td><td>12</td><td></td></tr><tr><td>83</td><td>2025-11-12 17:42:07</td><td>200</td><td>4546.7376708984375</td><td>12</td><td></td></tr><tr><td>93</td><td>2025-11-12 17:42:07</td><td>200</td><td>4543.737173080444</td><td>12</td><td></td></tr><tr><td>45</td><td>2025-11-12 17:42:07</td><td>200</td><td>4566.736698150635</td><td>12</td><td></td></tr><tr><td>35</td><td>2025-11-12 17:42:07</td><td>200</td><td>4573.734283447266</td><td>12</td><td></td></tr><tr><td>94</td><td>2025-11-12 17:42:07</td><td>200</td><td>4557.732820510864</td><td>12</td><td></td></tr><tr><td>67</td><td>2025-11-12 17:42:07</td><td>200</td><td>4570.733308792114</td><td>12</td><td></td></tr><tr><td>90</td><td>2025-11-12 17:42:07</td><td>200</td><td>4564.733028411865</td><td>12</td><td></td></tr><tr><td>66</td><td>2025-11-12 17:42:07</td><td>200</td><td>4574.735403060913</td><td>12</td><td></td></tr><tr><td>79</td><td>2025-11-12 17:42:07</td><td>200</td><td>4577.73232460022</td><td>12</td><td></td></tr><tr><td>96</td><td>2025-11-12 17:42:07</td><td>200</td><td>4572.735786437988</td><td>12</td><td></td></tr><tr><td>97</td><td>2025-11-12 17:42:07</td><td>200</td><td>4581.734418869019</td><td>12</td><td></td></tr><tr><td>70</td><td>2025-11-12 17:42:07</td><td>200</td><td>4596.734285354614</td><td>12</td><td></td></tr><tr><td>99</td><td>2025-11-12 17:42:07</td><td>200</td><td>4652.73118019104</td><td>12</td><td></td></tr><tr><td>88</td><td>2025-11-12 17:42:07</td><td>200</td><td>4658.735513687134</td><td>12</td><td></td></tr><tr><td>23</td><td>2025-11-12 17:42:07</td><td>200</td><td>4700.731992721558</td><td>12</td><td></td></tr><tr><td>18</td><td>2025-11-12 17:42:07</td><td>200</td><td>4704.733848571777</td><td>12</td><td></td></tr><tr><td>72</td><td>2025-11-12 17:42:07</td><td>200</td><td>4690.733194351196</td><td>12</td><td></td></tr>
        </table>
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052.summary.json
New file
Diff too large
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052_latency_hist.png
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251112_175052_rps.png
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050.details.csv
New file
Diff too large
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050.docx
Binary files differ
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050.html
New file
@@ -0,0 +1,50 @@
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>压测详细报告</title>
          <style>
            body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
            h2{color:#2c3e50}
            table{border-collapse:collapse; width:100%}
            th,td{padding:6px; text-align:left}
          </style>
        </head>
        <body>
          <h1>压测详细报告</h1>
        <h2>摘要</h2>
        <ul>
          <li>报告名称:压测详细报告</li>
          <li>生成时间:2025-11-14 11:10:51</li>
          <li>总请求数:10000</li>
          <li>成功数:10000,失败数:0,成功率:100.00%</li>
          <li>总耗时(秒):492.49</li>
          <li>平均吞吐(req/s):20.31</li>
        </ul>
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:3003.90</li>
          <li>最大:9071.00</li>
          <li>平均:4959.57</li>
          <li>中位数(P50):4861.27</li>
          <li>P90:5760.03,P95:6109.71,P99:6898.13</li>
        </ul>
          <h2>状态码分布</h2><ul><li>200: 10000</li></ul>
          <h2>错误汇总</h2><p>无错误记录</p>
          <h2>图表</h2><div><h3>latency_hist</h3><img src="动物房管理新建动物房压测任务_20251114_111050_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="动物房管理新建动物房压测任务_20251114_111050_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          <tr><td>53</td><td>2025-11-14 11:02:37</td><td>200</td><td>4442.166328430176</td><td>12</td><td></td></tr><tr><td>6</td><td>2025-11-14 11:02:37</td><td>200</td><td>4672.16682434082</td><td>12</td><td></td></tr><tr><td>1</td><td>2025-11-14 11:02:37</td><td>200</td><td>4678.151845932007</td><td>12</td><td></td></tr><tr><td>11</td><td>2025-11-14 11:02:37</td><td>200</td><td>4693.154811859131</td><td>12</td><td></td></tr><tr><td>2</td><td>2025-11-14 11:02:37</td><td>200</td><td>4707.14259147644</td><td>12</td><td></td></tr><tr><td>7</td><td>2025-11-14 11:02:37</td><td>200</td><td>4706.165790557861</td><td>12</td><td></td></tr><tr><td>5</td><td>2025-11-14 11:02:37</td><td>200</td><td>4709.152698516846</td><td>12</td><td></td></tr><tr><td>9</td><td>2025-11-14 11:02:37</td><td>200</td><td>4711.154937744141</td><td>12</td><td></td></tr><tr><td>4</td><td>2025-11-14 11:02:37</td><td>200</td><td>4731.152296066284</td><td>12</td><td></td></tr><tr><td>16</td><td>2025-11-14 11:02:37</td><td>200</td><td>4726.15647315979</td><td>12</td><td></td></tr><tr><td>3</td><td>2025-11-14 11:02:37</td><td>200</td><td>4736.144065856934</td><td>12</td><td></td></tr><tr><td>22</td><td>2025-11-14 11:02:37</td><td>200</td><td>4729.156970977783</td><td>12</td><td></td></tr><tr><td>15</td><td>2025-11-14 11:02:37</td><td>200</td><td>4750.155210494995</td><td>12</td><td></td></tr><tr><td>10</td><td>2025-11-14 11:02:37</td><td>200</td><td>4766.148805618286</td><td>12</td><td></td></tr><tr><td>23</td><td>2025-11-14 11:02:37</td><td>200</td><td>4761.157274246216</td><td>12</td><td></td></tr><tr><td>17</td><td>2025-11-14 11:02:37</td><td>200</td><td>4763.1566524505615</td><td>12</td><td></td></tr><tr><td>18</td><td>2025-11-14 11:02:37</td><td>200</td><td>4765.156030654907</td><td>12</td><td></td></tr><tr><td>29</td><td>2025-11-14 11:02:37</td><td>200</td><td>4765.155792236328</td><td>12</td><td></td></tr><tr><td>24</td><td>2025-11-14 11:02:37</td><td>200</td><td>4787.159204483032</td><td>12</td><td></td></tr><tr><td>8</td><td>2025-11-14 11:02:37</td><td>200</td><td>4798.150062561035</td><td>12</td><td></td></tr><tr><td>21</td><td>2025-11-14 11:02:37</td><td>200</td><td>4790.1575565338135</td><td>12</td><td></td></tr><tr><td>27</td><td>2025-11-14 11:02:37</td><td>200</td><td>4789.155721664429</td><td>12</td><td></td></tr><tr><td>20</td><td>2025-11-14 11:02:37</td><td>200</td><td>4792.155981063843</td><td>12</td><td></td></tr><tr><td>13</td><td>2025-11-14 11:02:37</td><td>200</td><td>4796.151638031006</td><td>12</td><td></td></tr><tr><td>42</td><td>2025-11-14 11:02:37</td><td>200</td><td>4800.156116485596</td><td>12</td><td></td></tr><tr><td>19</td><td>2025-11-14 11:02:37</td><td>200</td><td>4809.157133102417</td><td>12</td><td></td></tr><tr><td>44</td><td>2025-11-14 11:02:37</td><td>200</td><td>4801.157236099243</td><td>12</td><td></td></tr><tr><td>39</td><td>2025-11-14 11:02:37</td><td>200</td><td>4804.156064987183</td><td>12</td><td></td></tr><tr><td>50</td><td>2025-11-14 11:02:37</td><td>200</td><td>4801.158905029297</td><td>12</td><td></td></tr><tr><td>14</td><td>2025-11-14 11:02:37</td><td>200</td><td>4829.150438308716</td><td>12</td><td></td></tr><tr><td>64</td><td>2025-11-14 11:02:37</td><td>200</td><td>4812.152624130249</td><td>12</td><td></td></tr><tr><td>48</td><td>2025-11-14 11:02:37</td><td>200</td><td>4819.155693054199</td><td>12</td><td></td></tr><tr><td>30</td><td>2025-11-14 11:02:37</td><td>200</td><td>4825.156927108765</td><td>12</td><td></td></tr><tr><td>37</td><td>2025-11-14 11:02:37</td><td>200</td><td>4824.157476425171</td><td>12</td><td></td></tr><tr><td>12</td><td>2025-11-14 11:02:37</td><td>200</td><td>4837.149620056152</td><td>12</td><td></td></tr><tr><td>31</td><td>2025-11-14 11:02:37</td><td>200</td><td>4842.155933380127</td><td>12</td><td></td></tr><tr><td>45</td><td>2025-11-14 11:02:37</td><td>200</td><td>4838.156461715698</td><td>12</td><td></td></tr><tr><td>33</td><td>2025-11-14 11:02:37</td><td>200</td><td>4843.156099319458</td><td>12</td><td></td></tr><tr><td>40</td><td>2025-11-14 11:02:37</td><td>200</td><td>4842.155933380127</td><td>12</td><td></td></tr><tr><td>57</td><td>2025-11-14 11:02:37</td><td>200</td><td>4842.156887054443</td><td>12</td><td></td></tr><tr><td>25</td><td>2025-11-14 11:02:37</td><td>200</td><td>4854.153871536255</td><td>12</td><td></td></tr><tr><td>47</td><td>2025-11-14 11:02:37</td><td>200</td><td>4848.155498504639</td><td>12</td><td></td></tr><tr><td>26</td><td>2025-11-14 11:02:37</td><td>200</td><td>4864.153623580933</td><td>12</td><td></td></tr><tr><td>56</td><td>2025-11-14 11:02:37</td><td>200</td><td>4854.156255722046</td><td>12</td><td></td></tr><tr><td>41</td><td>2025-11-14 11:02:37</td><td>200</td><td>4869.156360626221</td><td>12</td><td></td></tr><tr><td>38</td><td>2025-11-14 11:02:37</td><td>200</td><td>4871.1559772491455</td><td>12</td><td></td></tr><tr><td>61</td><td>2025-11-14 11:02:37</td><td>200</td><td>4866.154193878174</td><td>12</td><td></td></tr><tr><td>36</td><td>2025-11-14 11:02:37</td><td>200</td><td>4881.155967712402</td><td>12</td><td></td></tr><tr><td>62</td><td>2025-11-14 11:02:37</td><td>200</td><td>4873.154640197754</td><td>12</td><td></td></tr><tr><td>46</td><td>2025-11-14 11:02:37</td><td>200</td><td>4881.155967712402</td><td>12</td><td></td></tr><tr><td>59</td><td>2025-11-14 11:02:37</td><td>200</td><td>4882.156133651733</td><td>12</td><td></td></tr><tr><td>34</td><td>2025-11-14 11:02:37</td><td>200</td><td>4892.156600952148</td><td>12</td><td></td></tr><tr><td>63</td><td>2025-11-14 11:02:37</td><td>200</td><td>4884.154558181763</td><td>12</td><td></td></tr><tr><td>76</td><td>2025-11-14 11:02:37</td><td>200</td><td>4883.150815963745</td><td>12</td><td></td></tr><tr><td>75</td><td>2025-11-14 11:02:37</td><td>200</td><td>4884.151935577393</td><td>12</td><td></td></tr><tr><td>80</td><td>2025-11-14 11:02:37</td><td>200</td><td>4885.152578353882</td><td>12</td><td></td></tr><tr><td>90</td><td>2025-11-14 11:02:37</td><td>200</td><td>4889.156341552734</td><td>12</td><td></td></tr><tr><td>43</td><td>2025-11-14 11:02:37</td><td>200</td><td>4907.156229019165</td><td>12</td><td></td></tr><tr><td>58</td><td>2025-11-14 11:02:37</td><td>200</td><td>4902.156352996826</td><td>12</td><td></td></tr><tr><td>52</td><td>2025-11-14 11:02:37</td><td>200</td><td>4911.156177520752</td><td>12</td><td></td></tr><tr><td>32</td><td>2025-11-14 11:02:37</td><td>200</td><td>4919.156074523926</td><td>12</td><td></td></tr><tr><td>69</td><td>2025-11-14 11:02:37</td><td>200</td><td>4906.151294708252</td><td>12</td><td></td></tr><tr><td>71</td><td>2025-11-14 11:02:37</td><td>200</td><td>4911.150217056274</td><td>12</td><td></td></tr><tr><td>97</td><td>2025-11-14 11:02:37</td><td>200</td><td>4905.157566070557</td><td>12</td><td></td></tr><tr><td>74</td><td>2025-11-14 11:02:38</td><td>200</td><td>4926.154136657715</td><td>12</td><td></td></tr><tr><td>96</td><td>2025-11-14 11:02:38</td><td>200</td><td>4919.156074523926</td><td>12</td><td></td></tr><tr><td>66</td><td>2025-11-14 11:02:38</td><td>200</td><td>4931.15234375</td><td>12</td><td></td></tr><tr><td>51</td><td>2025-11-14 11:02:38</td><td>200</td><td>4938.157558441162</td><td>12</td><td></td></tr><tr><td>55</td><td>2025-11-14 11:02:38</td><td>200</td><td>4937.156438827515</td><td>12</td><td></td></tr><tr><td>83</td><td>2025-11-14 11:02:38</td><td>200</td><td>4928.1580448150635</td><td>12</td><td></td></tr><tr><td>60</td><td>2025-11-14 11:02:38</td><td>200</td><td>4938.157081604004</td><td>12</td><td></td></tr><tr><td>35</td><td>2025-11-14 11:02:38</td><td>200</td><td>4959.1639041900635</td><td>12</td><td></td></tr><tr><td>100</td><td>2025-11-14 11:02:38</td><td>200</td><td>4938.153982162476</td><td>12</td><td></td></tr><tr><td>68</td><td>2025-11-14 11:02:38</td><td>200</td><td>4952.157497406006</td><td>12</td><td></td></tr><tr><td>87</td><td>2025-11-14 11:02:38</td><td>200</td><td>4947.15690612793</td><td>12</td><td></td></tr><tr><td>70</td><td>2025-11-14 11:02:38</td><td>200</td><td>4955.159902572632</td><td>12</td><td></td></tr><tr><td>85</td><td>2025-11-14 11:02:38</td><td>200</td><td>4951.161623001099</td><td>12</td><td></td></tr><tr><td>65</td><td>2025-11-14 11:02:38</td><td>200</td><td>4970.165491104126</td><td>12</td><td></td></tr><tr><td>54</td><td>2025-11-14 11:02:38</td><td>200</td><td>4976.1693477630615</td><td>12</td><td></td></tr><tr><td>98</td><td>2025-11-14 11:02:38</td><td>200</td><td>4961.169481277466</td><td>12</td><td></td></tr><tr><td>82</td><td>2025-11-14 11:02:38</td><td>200</td><td>4969.165563583374</td><td>12</td><td></td></tr><tr><td>73</td><td>2025-11-14 11:02:38</td><td>200</td><td>4973.166704177856</td><td>12</td><td></td></tr><tr><td>94</td><td>2025-11-14 11:02:38</td><td>200</td><td>4966.169357299805</td><td>12</td><td></td></tr><tr><td>92</td><td>2025-11-14 11:02:38</td><td>200</td><td>4981.168270111084</td><td>12</td><td></td></tr><tr><td>89</td><td>2025-11-14 11:02:38</td><td>200</td><td>4983.169317245483</td><td>12</td><td></td></tr><tr><td>91</td><td>2025-11-14 11:02:38</td><td>200</td><td>4984.169960021973</td><td>12</td><td></td></tr><tr><td>72</td><td>2025-11-14 11:02:38</td><td>200</td><td>4991.16849899292</td><td>12</td><td></td></tr><tr><td>77</td><td>2025-11-14 11:02:38</td><td>200</td><td>4990.165710449219</td><td>12</td><td></td></tr><tr><td>79</td><td>2025-11-14 11:02:38</td><td>200</td><td>4990.162372589111</td><td>12</td><td></td></tr><tr><td>67</td><td>2025-11-14 11:02:38</td><td>200</td><td>4996.165037155151</td><td>12</td><td></td></tr><tr><td>49</td><td>2025-11-14 11:02:38</td><td>200</td><td>5012.169599533081</td><td>12</td><td></td></tr><tr><td>88</td><td>2025-11-14 11:02:38</td><td>200</td><td>5001.169204711914</td><td>12</td><td></td></tr><tr><td>81</td><td>2025-11-14 11:02:38</td><td>200</td><td>5004.1663646698</td><td>12</td><td></td></tr><tr><td>78</td><td>2025-11-14 11:02:38</td><td>200</td><td>5010.161876678467</td><td>12</td><td></td></tr><tr><td>95</td><td>2025-11-14 11:02:38</td><td>200</td><td>5006.16979598999</td><td>12</td><td></td></tr><tr><td>86</td><td>2025-11-14 11:02:38</td><td>200</td><td>5021.169424057007</td><td>12</td><td></td></tr><tr><td>93</td><td>2025-11-14 11:02:38</td><td>200</td><td>5041.167974472046</td><td>12</td><td></td></tr><tr><td>84</td><td>2025-11-14 11:02:38</td><td>200</td><td>5073.168992996216</td><td>12</td><td></td></tr><tr><td>99</td><td>2025-11-14 11:02:38</td><td>200</td><td>5189.169406890869</td><td>12</td><td></td></tr><tr><td>28</td><td>2025-11-14 11:02:38</td><td>200</td><td>5306.169033050537</td><td>12</td><td></td></tr>
        </table>
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050.summary.json
New file
Diff too large
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050_latency_hist.png
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_111050_rps.png
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009.details.csv
New file
Diff too large
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009.docx
Binary files differ
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009.html
New file
@@ -0,0 +1,50 @@
        <!doctype html>
        <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>压测详细报告</title>
          <style>
            body{font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; padding:20px}
            h2{color:#2c3e50}
            table{border-collapse:collapse; width:100%}
            th,td{padding:6px; text-align:left}
          </style>
        </head>
        <body>
          <h1>压测详细报告</h1>
        <h2>摘要</h2>
        <ul>
          <li>报告名称:压测详细报告</li>
          <li>生成时间:2025-11-14 11:20:10</li>
          <li>总请求数:10000</li>
          <li>成功数:10000,失败数:0,成功率:100.00%</li>
          <li>总耗时(秒):521.91</li>
          <li>平均吞吐(req/s):19.16</li>
        </ul>
        <h2>响应时间统计 (ms)</h2>
        <ul>
          <li>最小:2943.98</li>
          <li>最大:9151.11</li>
          <li>平均:5255.09</li>
          <li>中位数(P50):5099.93</li>
          <li>P90:6211.76,P95:6406.13,P99:7407.30</li>
        </ul>
          <h2>状态码分布</h2><ul><li>200: 10000</li></ul>
          <h2>错误汇总</h2><p>无错误记录</p>
          <h2>图表</h2><div><h3>latency_hist</h3><img src="动物房管理新建动物房压测任务_20251114_112009_latency_hist.png" alt="latency_hist" style="max-width:100%;height:auto;"/></div><div><h3>rps</h3><img src="动物房管理新建动物房压测任务_20251114_112009_rps.png" alt="rps" style="max-width:100%;height:auto;"/></div>
        <h2>请求明细(仅显示前100条)</h2>
        <table border="1" cellpadding="4" cellspacing="0">
          <tr><th>#</th><th>时间</th><th>状态码</th><th>延迟(ms)</th><th>响应大小</th><th>错误</th></tr>
          <tr><td>3</td><td>2025-11-14 11:11:27</td><td>200</td><td>4755.320072174072</td><td>12</td><td></td></tr><tr><td>1</td><td>2025-11-14 11:11:27</td><td>200</td><td>4774.313926696777</td><td>12</td><td></td></tr><tr><td>6</td><td>2025-11-14 11:11:27</td><td>200</td><td>4778.31506729126</td><td>12</td><td></td></tr><tr><td>31</td><td>2025-11-14 11:11:27</td><td>200</td><td>4778.328418731689</td><td>12</td><td></td></tr><tr><td>10</td><td>2025-11-14 11:11:27</td><td>200</td><td>4789.316892623901</td><td>12</td><td></td></tr><tr><td>35</td><td>2025-11-14 11:11:27</td><td>200</td><td>4794.315814971924</td><td>12</td><td></td></tr><tr><td>12</td><td>2025-11-14 11:11:27</td><td>200</td><td>4802.316427230835</td><td>12</td><td></td></tr><tr><td>5</td><td>2025-11-14 11:11:27</td><td>200</td><td>4807.314872741699</td><td>12</td><td></td></tr><tr><td>14</td><td>2025-11-14 11:11:27</td><td>200</td><td>4811.316013336182</td><td>12</td><td></td></tr><tr><td>20</td><td>2025-11-14 11:11:27</td><td>200</td><td>4823.315382003784</td><td>12</td><td></td></tr><tr><td>24</td><td>2025-11-14 11:11:27</td><td>200</td><td>4822.31879234314</td><td>12</td><td></td></tr><tr><td>22</td><td>2025-11-14 11:11:27</td><td>200</td><td>4829.314708709717</td><td>12</td><td></td></tr><tr><td>33</td><td>2025-11-14 11:11:27</td><td>200</td><td>4827.317953109741</td><td>12</td><td></td></tr><tr><td>16</td><td>2025-11-14 11:11:27</td><td>200</td><td>4843.315839767456</td><td>12</td><td></td></tr><tr><td>4</td><td>2025-11-14 11:11:27</td><td>200</td><td>4852.316379547119</td><td>12</td><td></td></tr><tr><td>27</td><td>2025-11-14 11:11:27</td><td>200</td><td>4849.316596984863</td><td>12</td><td></td></tr><tr><td>41</td><td>2025-11-14 11:11:27</td><td>200</td><td>4846.314430236816</td><td>12</td><td></td></tr><tr><td>23</td><td>2025-11-14 11:11:27</td><td>200</td><td>4860.312700271606</td><td>12</td><td></td></tr><tr><td>53</td><td>2025-11-14 11:11:27</td><td>200</td><td>4861.315727233887</td><td>12</td><td></td></tr><tr><td>37</td><td>2025-11-14 11:11:27</td><td>200</td><td>4867.316961288452</td><td>12</td><td></td></tr><tr><td>25</td><td>2025-11-14 11:11:27</td><td>200</td><td>4879.315614700317</td><td>12</td><td></td></tr><tr><td>19</td><td>2025-11-14 11:11:27</td><td>200</td><td>4894.317150115967</td><td>12</td><td></td></tr><tr><td>30</td><td>2025-11-14 11:11:27</td><td>200</td><td>4891.315698623657</td><td>12</td><td></td></tr><tr><td>44</td><td>2025-11-14 11:11:27</td><td>200</td><td>4887.316465377808</td><td>12</td><td></td></tr><tr><td>15</td><td>2025-11-14 11:11:27</td><td>200</td><td>4902.318477630615</td><td>12</td><td></td></tr><tr><td>13</td><td>2025-11-14 11:11:27</td><td>200</td><td>4914.318561553955</td><td>12</td><td></td></tr><tr><td>50</td><td>2025-11-14 11:11:27</td><td>200</td><td>4903.316497802734</td><td>12</td><td></td></tr><tr><td>49</td><td>2025-11-14 11:11:27</td><td>200</td><td>4911.314964294434</td><td>12</td><td></td></tr><tr><td>40</td><td>2025-11-14 11:11:27</td><td>200</td><td>4915.313959121704</td><td>12</td><td></td></tr><tr><td>11</td><td>2025-11-14 11:11:27</td><td>200</td><td>4943.314790725708</td><td>12</td><td></td></tr><tr><td>32</td><td>2025-11-14 11:11:27</td><td>200</td><td>4948.315858840942</td><td>12</td><td></td></tr><tr><td>52</td><td>2025-11-14 11:11:27</td><td>200</td><td>4941.316843032837</td><td>12</td><td></td></tr><tr><td>29</td><td>2025-11-14 11:11:27</td><td>200</td><td>4963.313341140747</td><td>12</td><td></td></tr><tr><td>67</td><td>2025-11-14 11:11:27</td><td>200</td><td>4952.316999435425</td><td>12</td><td></td></tr><tr><td>42</td><td>2025-11-14 11:11:27</td><td>200</td><td>4967.312335968018</td><td>12</td><td></td></tr><tr><td>21</td><td>2025-11-14 11:11:27</td><td>200</td><td>4981.313705444336</td><td>12</td><td></td></tr><tr><td>51</td><td>2025-11-14 11:11:27</td><td>200</td><td>4985.3150844573975</td><td>12</td><td></td></tr><tr><td>69</td><td>2025-11-14 11:11:27</td><td>200</td><td>4982.314825057983</td><td>12</td><td></td></tr><tr><td>26</td><td>2025-11-14 11:11:27</td><td>200</td><td>5021.974325180054</td><td>12</td><td></td></tr><tr><td>28</td><td>2025-11-14 11:11:27</td><td>200</td><td>5021.504878997803</td><td>12</td><td></td></tr><tr><td>39</td><td>2025-11-14 11:11:27</td><td>200</td><td>5019.03772354126</td><td>12</td><td></td></tr><tr><td>7</td><td>2025-11-14 11:11:27</td><td>200</td><td>5036.609172821045</td><td>12</td><td></td></tr><tr><td>46</td><td>2025-11-14 11:11:27</td><td>200</td><td>5029.6080112457275</td><td>12</td><td></td></tr><tr><td>45</td><td>2025-11-14 11:11:27</td><td>200</td><td>5031.609773635864</td><td>12</td><td></td></tr><tr><td>17</td><td>2025-11-14 11:11:27</td><td>200</td><td>5040.610313415527</td><td>12</td><td></td></tr><tr><td>56</td><td>2025-11-14 11:11:27</td><td>200</td><td>5040.607929229736</td><td>12</td><td></td></tr><tr><td>60</td><td>2025-11-14 11:11:27</td><td>200</td><td>5042.60778427124</td><td>12</td><td></td></tr><tr><td>62</td><td>2025-11-14 11:11:27</td><td>200</td><td>5044.608116149902</td><td>12</td><td></td></tr><tr><td>65</td><td>2025-11-14 11:11:27</td><td>200</td><td>5053.607702255249</td><td>12</td><td></td></tr><tr><td>71</td><td>2025-11-14 11:11:27</td><td>200</td><td>5059.607744216919</td><td>12</td><td></td></tr><tr><td>9</td><td>2025-11-14 11:11:27</td><td>200</td><td>5080.612897872925</td><td>12</td><td></td></tr><tr><td>77</td><td>2025-11-14 11:11:27</td><td>200</td><td>5059.609651565552</td><td>12</td><td></td></tr><tr><td>86</td><td>2025-11-14 11:11:27</td><td>200</td><td>5062.608003616333</td><td>12</td><td></td></tr><tr><td>68</td><td>2025-11-14 11:11:27</td><td>200</td><td>5077.615737915039</td><td>12</td><td></td></tr><tr><td>82</td><td>2025-11-14 11:11:27</td><td>200</td><td>5078.694105148315</td><td>12</td><td></td></tr><tr><td>78</td><td>2025-11-14 11:11:27</td><td>200</td><td>5080.222129821777</td><td>12</td><td></td></tr><tr><td>58</td><td>2025-11-14 11:11:27</td><td>200</td><td>5088.218927383423</td><td>12</td><td></td></tr><tr><td>79</td><td>2025-11-14 11:11:27</td><td>200</td><td>5092.216014862061</td><td>12</td><td></td></tr><tr><td>88</td><td>2025-11-14 11:11:27</td><td>200</td><td>5091.216564178467</td><td>12</td><td></td></tr><tr><td>84</td><td>2025-11-14 11:11:27</td><td>200</td><td>5093.217134475708</td><td>12</td><td></td></tr><tr><td>83</td><td>2025-11-14 11:11:27</td><td>200</td><td>5097.214221954346</td><td>12</td><td></td></tr><tr><td>66</td><td>2025-11-14 11:11:27</td><td>200</td><td>5104.218006134033</td><td>12</td><td></td></tr><tr><td>96</td><td>2025-11-14 11:11:27</td><td>200</td><td>5097.215175628662</td><td>12</td><td></td></tr><tr><td>73</td><td>2025-11-14 11:11:27</td><td>200</td><td>5106.216192245483</td><td>12</td><td></td></tr><tr><td>72</td><td>2025-11-14 11:11:27</td><td>200</td><td>5121.214866638184</td><td>12</td><td></td></tr><tr><td>85</td><td>2025-11-14 11:11:27</td><td>200</td><td>5118.216037750244</td><td>12</td><td></td></tr><tr><td>99</td><td>2025-11-14 11:11:27</td><td>200</td><td>5118.216991424561</td><td>12</td><td></td></tr><tr><td>95</td><td>2025-11-14 11:11:27</td><td>200</td><td>5121.218204498291</td><td>12</td><td></td></tr><tr><td>34</td><td>2025-11-14 11:11:27</td><td>200</td><td>5140.2177810668945</td><td>12</td><td></td></tr><tr><td>81</td><td>2025-11-14 11:11:27</td><td>200</td><td>5131.2150955200195</td><td>12</td><td></td></tr><tr><td>100</td><td>2025-11-14 11:11:27</td><td>200</td><td>5126.216411590576</td><td>12</td><td></td></tr><tr><td>92</td><td>2025-11-14 11:11:27</td><td>200</td><td>5134.214162826538</td><td>12</td><td></td></tr><tr><td>80</td><td>2025-11-14 11:11:27</td><td>200</td><td>5139.2152309417725</td><td>12</td><td></td></tr><tr><td>63</td><td>2025-11-14 11:11:27</td><td>200</td><td>5145.216464996338</td><td>12</td><td></td></tr><tr><td>74</td><td>2025-11-14 11:11:27</td><td>200</td><td>5151.224613189697</td><td>12</td><td></td></tr><tr><td>75</td><td>2025-11-14 11:11:27</td><td>200</td><td>5153.218984603882</td><td>12</td><td></td></tr><tr><td>97</td><td>2025-11-14 11:11:27</td><td>200</td><td>5149.216413497925</td><td>12</td><td></td></tr><tr><td>55</td><td>2025-11-14 11:11:27</td><td>200</td><td>5163.216829299927</td><td>12</td><td></td></tr><tr><td>98</td><td>2025-11-14 11:11:27</td><td>200</td><td>5152.218341827393</td><td>12</td><td></td></tr><tr><td>59</td><td>2025-11-14 11:11:27</td><td>200</td><td>5176.215410232544</td><td>12</td><td></td></tr><tr><td>87</td><td>2025-11-14 11:11:27</td><td>200</td><td>5168.219327926636</td><td>12</td><td></td></tr><tr><td>36</td><td>2025-11-14 11:11:27</td><td>200</td><td>5186.21563911438</td><td>12</td><td></td></tr><tr><td>90</td><td>2025-11-14 11:11:27</td><td>200</td><td>5170.215606689453</td><td>12</td><td></td></tr><tr><td>70</td><td>2025-11-14 11:11:27</td><td>200</td><td>5178.2166957855225</td><td>12</td><td></td></tr><tr><td>91</td><td>2025-11-14 11:11:27</td><td>200</td><td>5176.214218139648</td><td>12</td><td></td></tr><tr><td>76</td><td>2025-11-14 11:11:27</td><td>200</td><td>5190.216302871704</td><td>12</td><td></td></tr><tr><td>8</td><td>2025-11-14 11:11:27</td><td>200</td><td>5214.215993881226</td><td>12</td><td></td></tr><tr><td>57</td><td>2025-11-14 11:11:27</td><td>200</td><td>5207.216024398804</td><td>12</td><td></td></tr><tr><td>2</td><td>2025-11-14 11:11:27</td><td>200</td><td>5255.218267440796</td><td>12</td><td></td></tr><tr><td>89</td><td>2025-11-14 11:11:27</td><td>200</td><td>5239.216327667236</td><td>12</td><td></td></tr><tr><td>93</td><td>2025-11-14 11:11:27</td><td>200</td><td>5274.2156982421875</td><td>12</td><td></td></tr><tr><td>38</td><td>2025-11-14 11:11:27</td><td>200</td><td>5383.217811584473</td><td>12</td><td></td></tr><tr><td>54</td><td>2025-11-14 11:11:27</td><td>200</td><td>5379.218578338623</td><td>12</td><td></td></tr><tr><td>43</td><td>2025-11-14 11:11:27</td><td>200</td><td>5412.217140197754</td><td>12</td><td></td></tr><tr><td>18</td><td>2025-11-14 11:11:27</td><td>200</td><td>5470.216512680054</td><td>12</td><td></td></tr><tr><td>48</td><td>2025-11-14 11:11:27</td><td>200</td><td>5502.215385437012</td><td>12</td><td></td></tr><tr><td>64</td><td>2025-11-14 11:11:27</td><td>200</td><td>5563.215732574463</td><td>12</td><td></td></tr><tr><td>47</td><td>2025-11-14 11:11:27</td><td>200</td><td>5583.215713500977</td><td>12</td><td></td></tr><tr><td>61</td><td>2025-11-14 11:11:28</td><td>200</td><td>5899.418354034424</td><td>12</td><td></td></tr><tr><td>94</td><td>2025-11-14 11:11:28</td><td>200</td><td>5927.4187088012695</td><td>12</td><td></td></tr>
        </table>
          <p>注:如需查看所有请求明细,请下载同目录下的 CSV/JSON 文件。</p>
        </body>
        </html>
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009.summary.json
New file
Diff too large
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009_latency_hist.png
测试组/脚本/造数脚本2/清华/load_test_report/动物房管理新建动物房压测任务_20251114_112009_rps.png
测试组/脚本/造数脚本2/清华/动物房管理新建动物房.py
New file
@@ -0,0 +1,240 @@
"""
集成压测脚本(带压测报告生成并通过钉钉发送摘要)
说明:
 - 该脚本基于之前的稳定 worker/队列 实现,运行后会记录每条请求的时间、状态码和延迟。
 - 运行结束后会调用Util目录下的压测报告生成器(文件名: stress_test_report_generator.py),输出 HTML/JSON/CSV/(可选)DOCX 等文件。
 - 生成后会把关键统计摘要通过 DingTalk 机器人发送(调用 DingTalkHelper.send_message)。
 - 安装依赖:aiohttp, tqdm, numpy/pandas/matplotlib/python-docx(可选)
 - 确保 DingTalkHelper 类在你的 `Util.dingtalk_helper` 中可用,且 ACCESS_TOKEN/SECRET 正确。
"""
import sys
import os
# 将上一级目录加入模块搜索路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio
import aiohttp
import time
import traceback
import datetime
from tqdm import tqdm
from Util.random_util import RandomUtil
from Util.dingtalk_helper import DingTalkHelper
# --- 配置 ---
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
apiname = "动物房管理新建动物房"
url = "http://tsinghua.baoyizn.com:9906/api/escrow/facilityroom/escrowRoom/save"
headers = {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjMxNzQ4OTksInVzZXJuYW1lIjoiZ2x5In0.3TIHTLrOt8vWZxT0SWsjPjaCfBHPyYdv-469Irl7rpU",
    "Content-Type": "application/json"
}
NUM_WORKERS = 100
TOTAL_REQUESTS = 10000
MAX_RETRIES = 3
REQUEST_TIMEOUT = 60
OUTPUT_DIR = './load_test_report'
# --- 初始化 ---
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
LARGE_CONTENT = "备注造数据" * 5
FILES_PATH = "/userfiles/1463828311460319233/程序附件//baoyi/individual/individualrecord/2025/10/cs.jpg"
def create_animal_data(idx: int):
    random_code = RandomUtil.generate_random_number_string(0, 10000)
    random_code_grade = RandomUtil.generate_random_number_string(1, 2)
    random_code_sex = RandomUtil.generate_random_number_string(1, 2)
    random_date = RandomUtil.generate_random_date("2023-01-01", "2025-10-16")
    return {
            "status": "1",
            "grade": {
                "id": "1915600066719633410"
            },
            "facilityId": "1915597153490292737",
            "id": "",
            "name": f"hyb测试房间{random_code},{random_date}",
            "address": "",
            "code": "",
            "type": "3",
            "roomType": "2",
            "user": {
                "id": ""
            },
            "remarks": LARGE_CONTENT,
            "parent": {
                "id": "1915597153762922498",
                "name": ""
            },
            "parentIds": "0,1915597153490292737,1915597153762922498",
            "sort": 1,
            "createType": "1",
            "researchGroup": {
                "id": ""
            }
        }
async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES):
    attempt = 0
    last_err = None
    while attempt < max_retries:
        data = create_animal_data(index)
        start = time.time()
        try:
            async with session.post(url, json=data, headers=headers) as resp:
                text = await resp.text()
                latency_ms = (time.time() - start) * 1000.0
                status = resp.status
                if status == 200:
                    return {
                        'index': index,
                        'timestamp': time.time(),
                        'status_code': status,
                        'latency_ms': latency_ms,
                        'response_size': len(text) if text is not None else None,
                        'error': None
                    }
                else:
                    last_err = f'status_{status}:{text}'
                    attempt += 1
                    await asyncio.sleep(min(10, 2 ** attempt))
        except Exception as e:
            latency_ms = (time.time() - start) * 1000.0
            last_err = f'{type(e).__name__}:{str(e)}'
            attempt += 1
            await asyncio.sleep(min(10, 2 ** attempt))
    # 最终失败
    return {
        'index': index,
        'timestamp': time.time(),
        'status_code': 0,
        'latency_ms': latency_ms if 'latency_ms' in locals() else 0,
        'response_size': None,
        'error': last_err
    }
async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock):
    while True:
        idx = await queue.get()
        if idx is None:
            queue.task_done()
            break
        try:
            res = await perform_request(session, idx)
            # 记录到报告生成器
            gen.record_result(
                index=res['index'],
                timestamp=res['timestamp'],
                status_code=int(res['status_code']),
                latency_ms=float(res['latency_ms']),
                response_size=res.get('response_size'),
                error=res.get('error')
            )
            async with lock:
                if res['status_code'] and 200 <= res['status_code'] < 300:
                    success_counter['count'] += 1
                else:
                    failed_list.append((res['index'], res.get('error')))
                pbar.update(1)
        except Exception as e:
            async with lock:
                failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}'))
                pbar.update(1)
        finally:
            queue.task_done()
async def batch_create_animals(total: int, num_workers: int):
    # 动态加载报告生成器模块(支持中文文件名)
    gen = None
    try:
        import importlib.util
        script_dir = os.path.dirname(os.path.abspath(__file__))
        report_path = os.path.join(script_dir, 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py')
        if os.path.exists(report_path):
            spec = importlib.util.spec_from_file_location('report_module', report_path)
            report_module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(report_module)
            LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator')
        else:
            # 备用:尝试直接导入模块名(若你的文件名已改为 ascii)
            from report_generator import LoadTestReportGenerator  # type: ignore
        gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告')
    except Exception as e:
        print('无法加载压测报告生成器,请确认stress_test_report_generator.py 文件位置正确。\n', e)
        raise
    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
    connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False)
    async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
        queue = asyncio.Queue()
        for i in range(1, total + 1):
            await queue.put(i)
        for _ in range(num_workers):
            await queue.put(None)
        success_counter = {'count': 0}
        failed_list = []
        lock = asyncio.Lock()
        with tqdm(total=total, desc='创建进度') as pbar:
            workers = [
                asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock))
                for i in range(num_workers)
            ]
            await asyncio.gather(*workers)
        # 任务完成,生成报告
        os.makedirs(OUTPUT_DIR, exist_ok=True)
        outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx'])
        stats = gen.compute_stats()
        # 构造钉钉摘要消息(中文)
        now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}']
        msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}")
        msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}")
        lat = stats.get('latency_ms', {})
        msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}")
        # 列出生成的报告文件
        file_list = []
        for k, v in outputs.items():
            if k == 'charts':
                for cname, cpath in v.items():
                    file_list.append(os.path.abspath(cpath))
            else:
                file_list.append(os.path.abspath(v))
        msg.append('生成文件:')
        for p in file_list:
            msg.append(p)
        final_msg = '\n'.join(msg)
        # 发送钉钉消息
        try:
            dingtalk_helper.send_message(final_msg)
        except Exception as e:
            print('发送钉钉消息失败:', e)
        print('\n[SUMMARY] 已生成报告并发送钉钉摘要。')
        print('成功数:', success_counter['count'], ' 失败数:', len(failed_list))
        if failed_list:
            print('失败示例(最多显示50条):')
            for idx, err in failed_list[:50]:
                print(f'  #{idx} => {err}')
if __name__ == '__main__':
    # 运行前建议先用小规模测试
    TOTAL_REQUESTS = TOTAL_REQUESTS
    NUM_WORKERS = NUM_WORKERS
    asyncio.run(batch_create_animals(TOTAL_REQUESTS, NUM_WORKERS))
测试组/脚本/造数脚本2/清华/我的动物.py
New file
@@ -0,0 +1,153 @@
import asyncio
import aiohttp
import traceback
import time
from Util.random_util import RandomUtil  # 保证该模块中提供了生成随机数字和随机日期的函数
from Util.dingtalk_helper import DingTalkHelper
from tqdm import tqdm  # 导入进度条库
# 钉钉机器人 access_token 和 secret
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
# 请求URL和请求头
apiname = "我的动物"
url = "http://tsinghua.baoyizn.com:9906/api/escrow/animal/escrowAnimal/save"
headers = {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDUwMzExNjAsInVzZXJuYW1lIjoiZ2x5In0.zra2vwwvl-NQKN_tyNzRbiF3kzfUQovXBuSmEGcXPFY",
    "Content-Type": "application/json"
}
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
gender = "1"
mother_id = ""
mother_wholeCode = ""
father_id = ""
father_wholeCode = ""
algebra = 1
preCode = "性能测试"
# 生成请求体
def create_animal_data():
    random_code = RandomUtil.generate_random_number_string(0, 999999999)
    random_date = RandomUtil.generate_random_date("2023-01-01", "2025-03-25")
    return {
        "bac": {"id": ""},
        "cage": {"id": "", "code": ""},
        "code": random_code,
        "room": {"id": ""},
        "user": {
            "id": "1780483551554764801",
            "name": "hyb实验人员"
        },
        "color": "4",
        "ethic": {
            "id": "1790288720729997312",
            "apCode": "24- Hybktfzr23.G24-4"
        },
        "shelf": {
            "id": "",
            "variety": {"id": ""}
        },
        "father": {
            "id": father_id,
            "wholeCode": father_wholeCode
        },
        "gender": gender,
        "mother": {
            "id": mother_id,
            "wholeCode": mother_wholeCode
        },
        "strain": {
            "id": "1846491449918550017",
            "bac": {"id": "", "name": "转基因"},
            "name": "hyb无需鉴定基因型,求求勿动",
            "type": "1",
            "maxNum": 5,
            "geneIds": {"id": "1810583565757919233,1810583743961313281,1810583743961313281,1810583743961313281,1860972233362567169"},
            "variety": {
                "id": "",
                "name": "SPF级基因鼠(荧光蛋白标记小鼠)",
                "isChip": "1"
            }
        },
        "algebra": algebra,
        "preCode": preCode ,
        "variety": {"id": "1661975536839733250"},
        "weaning": "2",
        "chipCode": random_code,
        "genotype": "hyb管理员创建共享,求求勿动 +/-/Tg/WT,hyb管理员创建使用者为课题负责人共享,求求勿动 +/-/Homo/Heter/WT",
        "transfer": "0",
        "birthDate": random_date,
        "wholeCode": f"{preCode}-{random_code}",
        "algebraWay": "1",
        "animalStatus": "0",
        "escrowStatus": "1",
        "researchGroup": {"id": "1699404304041828353"}
    }
# 异步发送单个请求,并返回是否成功
async def create_animal(session: aiohttp.ClientSession, index: int, max_retries: int = 3):
    data = create_animal_data()
    attempt = 0
    while attempt < max_retries:
        try:
            async with session.post(url, json=data, headers=headers) as response:
                resp_text = await response.text()
                if response.status == 200:
                    print(f"[INFO] 第 {index} 个动物创建成功,响应:{resp_text}")
                    return True
                else:
                    print(f"[ERROR] 第 {index} 个动物创建失败,状态码:{response.status},响应:{resp_text}")
                    return False
        except Exception as e:
            attempt += 1
            status_code = getattr(e, 'status', 'N/A')
            error_trace = traceback.format_exc()
            full_log = (
                f"请求URL: {url}\n"
                f"请求头: {headers}\n"
                f"请求数据: {data}\n"
                f"状态码: {status_code}\n"
                f"异常信息: {str(e)}\n"
                f"堆栈跟踪:\n{error_trace}\n"
                f"重试次数: {attempt}/{max_retries}"
            )
            print(f"[EXCEPTION] 第 {index} 个动物请求异常:\n{full_log}\n")
            if attempt < max_retries:
                # 重试前等待 1 秒
                await asyncio.sleep(1)
            else:
                return False
# 批量异步创建动物信息,同时显示进度条
async def batch_create_animals(batch_size: int):
    tasks = []
    # 设置超时时间为60秒(可根据需要调整)
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        for i in range(1, batch_size + 1):
            task = asyncio.ensure_future(create_animal(session, i))
            tasks.append(task)
        success_count = 0
        # 使用 asyncio.as_completed 逐个等待任务完成,并更新进度条
        with tqdm(total=batch_size, desc="创建动物进度") as pbar:
            for finished in asyncio.as_completed(tasks):
                result = await finished
                if result:
                    success_count += 1
                pbar.update(1)
        summary = f"\n[SUMMARY] 成功创建 {success_count}/{batch_size} 只动物"
        print(summary)
        dingtalk_helper.send_message(
            f"你的批量创建{apiname}模块数据已完成:成功创建 {success_count}/{batch_size} 只动物")
# 主入口
if __name__ == '__main__':
    n = 10  # 可根据需要调整批量创建数量
    asyncio.run(batch_create_animals(n))
测试组/脚本/造数脚本2/齐鲁/TestData/userinfo.xlsx
Binary files differ
测试组/脚本/造数脚本2/齐鲁/load_test_report/齐鲁医院pda更新笼位性能测试报告.docx
Binary files differ
测试组/脚本/造数脚本2/齐鲁/pda更新笼位.py
New file
@@ -0,0 +1,402 @@
"""
pad笼位更新压测脚本(带压测报告生成并通过钉钉发送摘要)
说明:
 - 该脚本模拟多个饲养员同时对笼位进行频繁更新操作
 - 从userinfo.xlsx读取多个用户token实现多用户并发压测
 - 从数据库动态获取笼位ID、用户ID和课题组ID
 - 运行后会记录每条请求的时间、状态码和延迟
 - 运行结束后会生成压测报告并通过钉钉发送摘要
"""
import sys
import os
# 将上一级目录加入模块搜索路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio
import aiohttp
import time
import traceback
import datetime
import pandas as pd
import pymysql
import random
from tqdm import tqdm
from Util.dingtalk_helper import DingTalkHelper
# 将上一级目录加入模块搜索路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# --- 配置 ---
ACCESS_TOKEN = '4625f6690acd9347fae5b3a05af598be63e73d604b933a9b3902425b8f136d4d'
SECRET = 'SEC3b6937550bd297b5491855f6f40c2ff1b41bc8c495e118ba9848742b1ddf8f19'
apiname = "pda笼位更新"
url = "http://qilu.baoyizn.com:9935/api/base/cage/cage/trainingCage"
headers = {
    "user-agent": "Mozilla/5.0 (Linux; Android 10; DS-MDT301 Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.186 Mobile Safari/537.36 uni-app Html5Plus/1.0 (Immersed/24.0)",
    "Content-Type": "application/json; charset=utf-8",
    "Accept-Encoding": "gzip"
}
NUM_WORKERS = 100
TOTAL_REQUESTS = 20000
MAX_RETRIES = 3
REQUEST_TIMEOUT = 60
OUTPUT_DIR = './load_test_report'
# --- 数据库配置 ---
# DB_CONFIG = {
#     'host': 'rm-wz97y18i4t16hfsk6qo.mysql.rds.aliyuncs.com',
#     'database': 'srps_qilu',
#     'user': 'dev',
#     'password': 'Hello@112',
#     'charset': 'utf8mb4'
# }
DB_CONFIG = {
    'host': 'rm-wz90i533p18631e685o.mysql.rds.aliyuncs.com',
    'database': 'srps_qilu_prod',
    'user': 'dev',
    'password': 'Hello@112',
    'charset': 'utf8mb4'
}
# --- 初始化 ---
dingtalk_helper = DingTalkHelper(ACCESS_TOKEN, SECRET)
class DataManager:
    """数据管理器,负责从数据库和Excel加载数据"""
    def __init__(self):
        self.cage_ids = []
        self.user_data = []  # 存储用户ID和对应的课题组ID
        self.user_tokens = []  # 存储用户token
    def load_user_tokens(self, excel_file='TestData/userinfo.xlsx'):
        """从Excel加载用户token"""
        try:
            df = pd.read_excel(excel_file)
            self.user_tokens = df['token'].tolist()
            print(f"成功加载 {len(self.user_tokens)} 个用户token")
            return True
        except Exception as e:
            print(f"加载用户token失败: {e}")
            return False
    def load_database_data(self):
        """从数据库加载笼位ID和用户信息"""
        try:
            conn = pymysql.connect(**DB_CONFIG)
            # 获取笼位ID
            with conn.cursor() as cursor:
                cursor.execute("SELECT id FROM l_cage WHERE id IS NOT NULL")
                self.cage_ids = [row[0] for row in cursor.fetchall()]
                print(f"成功加载 {len(self.cage_ids)} 个笼位ID")
            # 获取用户ID和课题组ID
            with conn.cursor() as cursor:
                cursor.execute("SELECT id, research_group_ids FROM sys_user WHERE research_group_ids IS NOT NULL AND research_group_ids != ''")
                for user_id, research_groups in cursor.fetchall():
                    # 处理多个课题组ID(用逗号分隔的情况)
                    if research_groups and ',' in research_groups:
                        group_ids = [gid.strip() for gid in research_groups.split(',') if gid.strip()]
                    elif research_groups:
                        group_ids = [research_groups.strip()]
                    else:
                        continue
                    self.user_data.append({
                        'user_id': user_id,
                        'research_group_ids': group_ids
                    })
                print(f"成功加载 {len(self.user_data)} 个有效用户数据")
            conn.close()
            return len(self.cage_ids) > 0 and len(self.user_data) > 0
        except Exception as e:
            print(f"数据库连接失败: {e}")
            return False
    def get_random_cage_id(self):
        """随机获取一个笼位ID"""
        return random.choice(self.cage_ids) if self.cage_ids else None
    def get_random_user_data(self):
        """随机获取一个用户数据(用户ID和课题组ID)"""
        if not self.user_data:
            return None, None
        user = random.choice(self.user_data)
        user_id = user['user_id']
        research_group_id = random.choice(user['research_group_ids'])
        return user_id, research_group_id
    def get_random_token(self):
        """随机获取一个用户token"""
        return random.choice(self.user_tokens) if self.user_tokens else None
# 全局数据管理器
data_manager = DataManager()
def create_cage_data():
    """创建笼位更新请求数据"""
    cage_id = data_manager.get_random_cage_id()
    user_id, research_group_id = data_manager.get_random_user_data()
    if not cage_id or not user_id or not research_group_id:
        return None
    return {
        "cage": {"id": str(cage_id)},
        "researchGroup": {"id": research_group_id},
        "user": {"id": user_id}
    }
async def perform_request(session: aiohttp.ClientSession, index: int, max_retries: int = MAX_RETRIES):
    """执行单个请求"""
    attempt = 0
    last_err = None
    token = data_manager.get_random_token()
    if not token:
        return {
            'index': index,
            'timestamp': time.time(),
            'status_code': 0,
            'latency_ms': 0,
            'response_size': None,
            'error': 'No available token'
        }
    # 动态设置token到headers
    current_headers = headers.copy()
    current_headers['token'] = token
    while attempt < max_retries:
        data = create_cage_data()
        if not data:
            return {
                'index': index,
                'timestamp': time.time(),
                'status_code': 0,
                'latency_ms': 0,
                'response_size': None,
                'error': 'No available cage/user data'
            }
        start = time.time()
        try:
            async with session.post(url, json=data, headers=current_headers) as resp:
                text = await resp.text()
                latency_ms = (time.time() - start) * 1000.0
                status = resp.status
                if status == 200:
                    return {
                        'index': index,
                        'timestamp': time.time(),
                        'status_code': status,
                        'latency_ms': latency_ms,
                        'response_size': len(text) if text is not None else None,
                        'error': None
                    }
                else:
                    last_err = f'status_{status}:{text}'
                    attempt += 1
                    await asyncio.sleep(min(10, 2 ** attempt))
        except Exception as e:
            latency_ms = (time.time() - start) * 1000.0
            last_err = f'{type(e).__name__}:{str(e)}'
            attempt += 1
            await asyncio.sleep(min(10, 2 ** attempt))
    # 最终失败
    return {
        'index': index,
        'timestamp': time.time(),
        'status_code': 0,
        'latency_ms': latency_ms if 'latency_ms' in locals() else 0,
        'response_size': None,
        'error': last_err
    }
async def worker(name: int, queue: asyncio.Queue, session: aiohttp.ClientSession, gen, pbar, success_counter: dict, failed_list: list, lock: asyncio.Lock):
    """工作线程"""
    while True:
        idx = await queue.get()
        if idx is None:
            queue.task_done()
            break
        try:
            res = await perform_request(session, idx)
            # 记录到报告生成器
            if gen:
                gen.record_result(
                    index=res['index'],
                    timestamp=res['timestamp'],
                    status_code=int(res['status_code']),
                    latency_ms=float(res['latency_ms']),
                    response_size=res.get('response_size'),
                    error=res.get('error')
                )
            async with lock:
                if res['status_code'] and 200 <= res['status_code'] < 300:
                    success_counter['count'] += 1
                else:
                    failed_list.append((res['index'], res.get('error')))
                pbar.update(1)
        except Exception as e:
            async with lock:
                failed_list.append((idx, f'Worker异常:{type(e).__name__}:{e}'))
                pbar.update(1)
        finally:
            queue.task_done()
async def batch_update_cages(total: int, num_workers: int):
    """批量更新笼位"""
    # 动态加载报告生成器模块
    gen = None
    try:
        import importlib.util
        # 使用您提供的绝对路径
        report_path = 'H:\\项目\\造数脚本\\Util\\stress_test_report_generator.py'
        if os.path.exists(report_path):
            spec = importlib.util.spec_from_file_location('report_module', report_path)
            report_module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(report_module)
            LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator')
            gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告')
            print(f"成功加载压测报告生成器: {report_path}")
        else:
            # 备用:尝试相对路径
            script_dir = os.path.dirname(os.path.abspath(__file__))
            report_path = os.path.join(script_dir, 'Util', 'stress_test_report_generator.py')
            if os.path.exists(report_path):
                spec = importlib.util.spec_from_file_location('report_module', report_path)
                report_module = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(report_module)
                LoadTestReportGenerator = getattr(report_module, 'LoadTestReportGenerator')
                gen = LoadTestReportGenerator(test_name=f'{apiname}压测任务', report_title='压测详细报告')
                print(f"成功加载压测报告生成器: {report_path}")
            else:
                print('警告: 未找到压测报告生成器,将跳过报告生成')
    except Exception as e:
        print(f'加载压测报告生成器失败: {e},将跳过报告生成')
    # 初始化数据
    if not data_manager.load_user_tokens():
        print("加载用户token失败,退出压测")
        return
    if not data_manager.load_database_data():
        print("加载数据库数据失败,退出压测")
        return
    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
    connector = aiohttp.TCPConnector(limit=num_workers, limit_per_host=num_workers, force_close=False)
    async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
        queue = asyncio.Queue()
        for i in range(1, total + 1):
            await queue.put(i)
        for _ in range(num_workers):
            await queue.put(None)
        success_counter = {'count': 0}
        failed_list = []
        lock = asyncio.Lock()
        with tqdm(total=total, desc='压测进度') as pbar:
            workers = [
                asyncio.create_task(worker(i, queue, session, gen, pbar, success_counter, failed_list, lock))
                for i in range(num_workers)
            ]
            await asyncio.gather(*workers)
        # 任务完成,生成报告
        if gen:
            os.makedirs(OUTPUT_DIR, exist_ok=True)
            outputs = gen.generate_report(OUTPUT_DIR, formats=['html', 'json', 'csv', 'docx'])
            stats = gen.compute_stats()
            # 构造钉钉摘要消息
            now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}']
            msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}")
            if 'duration_seconds' in stats:
                msg.append(f"总耗时(s):{stats.get('duration_seconds',0):.2f},平均吞吐(req/s):{stats.get('throughput_rps',0):.2f}")
            if 'latency_ms' in stats:
                lat = stats.get('latency_ms', {})
                msg.append(f"延迟(ms) - 平均:{lat.get('avg',0):.2f},P90:{lat.get('p90',0):.2f},P95:{lat.get('p95',0):.2f},P99:{lat.get('p99',0):.2f}")
            msg.append(f"使用用户数:{len(data_manager.user_tokens)}")
            msg.append(f"可用笼位数:{len(data_manager.cage_ids)}")
            msg.append(f"有效用户数:{len(data_manager.user_data)}")
            # 列出生成的报告文件
            if outputs:
                file_list = []
                for k, v in outputs.items():
                    if k == 'charts':
                        for cname, cpath in v.items():
                            file_list.append(os.path.abspath(cpath))
                    else:
                        file_list.append(os.path.abspath(v))
                msg.append('生成文件:')
                for p in file_list:
                    msg.append(p)
            final_msg = '\n'.join(msg)
        else:
            # 如果没有报告生成器,计算基础统计
            stats = {
                'total_requests': total,
                'success_count': success_counter['count'],
                'fail_count': len(failed_list),
                'success_rate': success_counter['count'] / total if total > 0 else 0
            }
            # 基础统计消息
            now_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            msg = [f'【{apiname} 压测报告】', f'生成时间:{now_str}']
            msg.append(f"总请求数:{stats.get('total_requests',0)},成功:{stats.get('success_count',0)},失败:{stats.get('fail_count',0)},成功率:{stats.get('success_rate',0):.2%}")
            msg.append(f"使用用户数:{len(data_manager.user_tokens)}")
            msg.append(f"可用笼位数:{len(data_manager.cage_ids)}")
            msg.append(f"有效用户数:{len(data_manager.user_data)}")
            final_msg = '\n'.join(msg)
        # 发送钉钉消息
        try:
            dingtalk_helper.send_message(final_msg)
            print('钉钉消息发送成功')
        except Exception as e:
            print('发送钉钉消息失败:', e)
        print('\n[压测摘要]')
        print('成功数:', success_counter['count'], ' 失败数:', len(failed_list))
        if failed_list:
            print('失败示例(最多显示20条):')
            for idx, err in failed_list[:20]:
                print(f'  #{idx} => {err}')
if __name__ == '__main__':
    # 运行前检查依赖
    try:
        import pymysql
        import pandas
        print("依赖检查通过,开始压测...")
    except ImportError as e:
        print(f"缺少依赖: {e}")
        print("请安装: pip install pymysql pandas")
        exit(1)
    # 运行压测
    asyncio.run(batch_update_cages(TOTAL_REQUESTS, NUM_WORKERS))