Загрузка данных
package org.example.service;
import org.example.model.CsvRow;
import org.example.model.OldProfileData;
import org.example.model.ProfileEntry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors; // ИЗМЕНЕНО: добавлен импорт Collectors
@Slf4j
@Service
public class CsvGeneratorService {
private static final int DEFAULT_SLA = 3;
private static final String STATIC_URL = "/ic/ufs/corp-retail/{{version}}/js/app/corp-retail-mf.js";
private static final String STATIC_OPERATION_ID = "UC00_StaticJS_1_WEB";
private static final String GET_CONTACTS_OPERATION_ID = "UC29_GET_contacts_WEB";
private static final int STATIC_INTENSITY_DEFAULT = 300000;
private static final int STATIC_SLA_DEFAULT = 1;
@Autowired
private UriNormalizer uriNormalizer;
private String urlPrefix;
private String projectType;
private Set<String> lastExtraInScript = new HashSet<>();
public Set<String> getLastExtraInScript() {
return lastExtraInScript;
}
public List<CsvRow> generateProfile(Map<String, List<String>> urlToOpIds,
List<ProfileEntry> profileEntries,
Map<String, OldProfileData> lastProfileData,
String outputFolder,
String outputFilename,
String requirement,
String urlPrefix,
String projectType) {
this.urlPrefix = urlPrefix;
this.projectType = projectType;
// Маппинг: "method|normalizedUri" -> operationId из прошлого профиля
Map<String, String> oldUriToOpId = new HashMap<>();
if (lastProfileData != null) {
for (Map.Entry<String, OldProfileData> oldEntry : lastProfileData.entrySet()) {
String oldOpId = oldEntry.getKey();
OldProfileData oldData = oldEntry.getValue();
if (oldData.getFullService() != null && oldData.getFullService().contains(" ")) {
String oldMethod = oldData.getFullService().substring(0, oldData.getFullService().indexOf(' '));
String oldUri = oldData.getFullService().substring(oldData.getFullService().indexOf(' ') + 1);
String normalizedOldUri = uriNormalizer.normalizeUri(oldUri, projectType);
oldUriToOpId.put(oldMethod.toUpperCase() + "|" + normalizedOldUri, oldOpId);
}
}
}
List<CsvRow> rowsForCsv = new ArrayList<>(); // для CSV (intensity > 0)
List<CsvRow> allRowsForExcel = new ArrayList<>(); // для Excel (все операции)
// ========== 1. Статический запрос (ТОЛЬКО ДЛЯ WEB) ==========
if ("WEB".equals(projectType)) {
int staticIntensity = STATIC_INTENSITY_DEFAULT;
int staticSla = STATIC_SLA_DEFAULT;
if (lastProfileData.containsKey(STATIC_OPERATION_ID)) {
OldProfileData data = lastProfileData.get(STATIC_OPERATION_ID);
staticIntensity = data.getIntensity();
staticSla = data.getSla();
log.info(" Найдена в прошлом профиле {}: интенсивность = {}, SLA = {} сек",
STATIC_OPERATION_ID, staticIntensity, staticSla);
} else {
log.info(" ⚠️ Не найдена в прошлом профиле {}, используем значения по умолчанию: инт = {}, SLA = {} сек",
STATIC_OPERATION_ID, staticIntensity, staticSla);
}
CsvRow staticRow = new CsvRow(
STATIC_OPERATION_ID,
STATIC_URL,
"GET",
staticIntensity,
staticSla,
STATIC_OPERATION_ID,
requirement,
""
);
rowsForCsv.add(staticRow);
allRowsForExcel.add(staticRow);
log.info(" Добавлен статический запрос: {} (инт: {}, SLA: {})",
STATIC_OPERATION_ID, staticIntensity, staticSla);
} else if ("MOBILE".equals(projectType)) {
log.debug(" Статический запрос не добавляется для проекта: {}", projectType);
} else if ("PPRB".equals(projectType)) {
log.debug(" Статический запрос не добавляется для проекта: {}", projectType);
}
// ========== 2. Собираем URI из профиля аналитика ==========
Map<String, ProfileEntry> profileEntryMap = new HashMap<>();
for (ProfileEntry entry : profileEntries) {
if (entry.getIntensity() > 0) {
String key;
if ("PPRB".equals(projectType)) {
String normalizedUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
key = entry.getMethod() + "|" + normalizedUri;
} else {
key = entry.getMethod() + "|" + entry.getUri();
}
profileEntryMap.put(key, entry);
}
}
// ========== 3. Исключаем статический URL из проверок (только для Web) ==========
int totalScriptOperations = 0;
for (List<String> opIds : urlToOpIds.values()) {
totalScriptOperations += opIds.size();
}
Map<String, List<String>> filteredUrlToOpIds = new HashMap<>();
boolean removedStatic = false;
for (Map.Entry<String, List<String>> entry : urlToOpIds.entrySet()) {
if ("WEB".equals(projectType) && entry.getKey().equals(STATIC_URL)) {
removedStatic = true;
continue;
}
filteredUrlToOpIds.put(entry.getKey(), entry.getValue());
}
int scriptOperationsAfterRemoval = 0;
for (List<String> opIds : filteredUrlToOpIds.values()) {
scriptOperationsAfterRemoval += opIds.size();
}
log.info(" Всего операций в скрипте: {}", totalScriptOperations);
if (removedStatic) {
log.info(" Удален статический запрос: {}", STATIC_URL);
log.info(" Скрипт после исключения статики: {} операций", scriptOperationsAfterRemoval);
} else {
log.info(" Скрипт: {} операций", scriptOperationsAfterRemoval);
}
// ========== 4. Находим операции, которые есть в скрипте, но НЕТ в профиле ==========
Set<String> scriptKeys = new HashSet<>();
for (Map.Entry<String, List<String>> scriptEntry : filteredUrlToOpIds.entrySet()) {
String scriptUri = scriptEntry.getKey();
for (String opId : scriptEntry.getValue()) {
String methodFromOpId = extractMethodFromOperationId(opId);
String key;
if ("PPRB".equals(projectType)) {
String normalizedUri = uriNormalizer.normalizeUri(scriptUri, projectType);
key = methodFromOpId + "|" + normalizedUri;
} else {
key = methodFromOpId + "|" + scriptUri;
}
scriptKeys.add(key);
}
}
Set<String> profileKeys = new HashSet<>();
for (ProfileEntry entry : profileEntries) {
if (entry.getIntensity() > 0) {
String key;
if ("PPRB".equals(projectType)) {
String normalizedUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
key = entry.getMethod() + "|" + normalizedUri;
} else {
key = entry.getMethod() + "|" + entry.getUri();
}
profileKeys.add(key);
}
}
Set<String> extraInScript = new HashSet<>(scriptKeys);
extraInScript.removeAll(profileKeys);
this.lastExtraInScript = extraInScript;
if (!extraInScript.isEmpty()) {
log.info("\n⚠️ ВНИМАНИЕ! Операции, которые есть в скрипте, но ОТСУТСТВУЮТ в профиле аналитика:");
log.info(" (нужно удалить из скрипта)");
log.info("========================================");
for (String key : extraInScript) {
String[] parts = key.split("\\|", 2);
String method = parts[0];
String uri = parts[1];
List<String> opIds = filteredUrlToOpIds.get(uri);
log.info(" {} {} → {}", method, uri, opIds);
}
log.info("========================================");
log.info(" Всего лишних операций в скрипте: {}\n", extraInScript.size());
} else {
log.info("\n Все операции из скрипта присутствуют в профиле аналитика\n");
}
// ========== 5. Находим операции, которые есть в профиле, но НЕТ в скрипте ==========
Set<String> extraInProfile = new HashSet<>(profileKeys);
extraInProfile.removeAll(scriptKeys);
if (!extraInProfile.isEmpty()) {
log.info("\n СПИСОК: Операции, которые есть в профиле аналитика, но ОТСУТСТВУЮТ в скрипте:");
log.info(" (будут созданы как новые запросы)");
log.info("========================================");
for (String key : extraInProfile) {
String[] parts = key.split("\\|", 2);
String method = parts[0];
String uri = parts[1];
log.info(" {} {} (интенсивность: {})", method, uri,
profileEntryMap.get(key).getIntensity());
}
log.info("========================================");
log.info(" Всего новых операций (нужно создать): {}\n", extraInProfile.size());
} else {
log.info("\n Все операции из профиля аналитика присутствуют в скрипте\n");
}
log.debug("\n=== Сравнение с нормализацией ===");
for (ProfileEntry entry : profileEntries) {
if (entry.getIntensity() == 0) continue;
String normalizedProfileUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
boolean found = false;
String matchedScriptUri = null;
for (String scriptUri : filteredUrlToOpIds.keySet()) {
String normalizedScriptUri = uriNormalizer.normalizeUri(scriptUri, projectType);
if (normalizedScriptUri.equals(normalizedProfileUri)) {
found = true;
matchedScriptUri = scriptUri;
break;
}
}
if (!found) {
log.warn(" НЕ СОВПАДАЕТ: Профиль='{}' -> нормализовано='{}'",
entry.getUri(), normalizedProfileUri);
}
else {
log.debug(" СОВПАДАЕТ: Профиль='{}' -> Скрипт='{}'", entry.getUri(), matchedScriptUri);
}
}
log.info("========================================\n");
// ========== 6. Обрабатываем операции из профиля аналитика ==========
Set<String> usedOpIds = new HashSet<>();
for (List<String> opIds : filteredUrlToOpIds.values()) {
usedOpIds.addAll(opIds);
}
if ("WEB".equals(projectType)) {
usedOpIds.add(STATIC_OPERATION_ID);
}
Set<String> matchedOpIds = new HashSet<>();
int nextUcNum = getNextUcNumber(usedOpIds);
int matchedCount = 0;
int newCount = 0;
int validProfileEntriesCount = 0;
log.debug("--- Обработка операций из профиля аналитика ---");
for (ProfileEntry entry : profileEntries) {
log.debug(" Обработка entry: метод={}, uri={}, интенсивность={}", entry.getMethod(), entry.getUri(), entry.getIntensity());
if (entry.getIntensity() > 0) {
validProfileEntriesCount++;
} else {
log.info(" Операция с нулевой интенсивностью: {} {} - будет покрашена красным в Excel",
entry.getMethod(), entry.getUri());
}
String operationId = null;
String profileMethod = entry.getMethod();
if (filteredUrlToOpIds.containsKey(entry.getUri())) {
List<String> opIds = filteredUrlToOpIds.get(entry.getUri());
for (String opId : opIds) {
if (!matchedOpIds.contains(opId)) {
String opMethod = extractMethodFromOperationId(opId);
if (opMethod.equals(profileMethod)) {
operationId = opId;
matchedOpIds.add(opId);
break;
}
}
}
if (operationId == null) {
for (String opId : opIds) {
if (!matchedOpIds.contains(opId)) {
operationId = opId;
matchedOpIds.add(opId);
if (entry.getIntensity() > 0) {
log.warn(" ⚠️ Метод не совпадает! Профиль: {}, operationId: {} (URI: {})",
profileMethod, opId, entry.getUri());
}
break;
}
}
}
if (operationId != null) {
if (entry.getIntensity() > 0) {
if (opIds.size() > 1) {
log.debug(" Найден в скрипте (один из {}): {} {} → {} (инт: {})",
opIds.size(), profileMethod, entry.getUri(), operationId, entry.getIntensity());
} else {
log.debug(" Найден в скрипте: {} {} → {} (инт: {})",
profileMethod, entry.getUri(), operationId, entry.getIntensity());
}
matchedCount++;
}
} else {
// Все operation_id для URI уже использованы — ищем в прошлом профиле
String normalizedEntryUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
String oldKey = entry.getMethod().toUpperCase() + "|" + normalizedEntryUri;
String existingOpId = oldUriToOpId.get(oldKey);
if (existingOpId != null && !usedOpIds.contains(existingOpId)) {
operationId = existingOpId;
usedOpIds.add(operationId);
nextUcNum = Math.max(nextUcNum, extractUcNumber(operationId) + 1);
if (entry.getIntensity() > 0) {
log.info(" Восстановлен из прошлого профиля (URI в скрипте): {} {} → {} (инт: {})",
profileMethod, entry.getUri(), operationId, entry.getIntensity());
matchedCount++;
}
} else {
if (entry.getIntensity() > 0) {
log.warn(" ⚠️ Все operation_id для URI {} уже использованы. Создаём новый.", entry.getUri());
}
operationId = generateNewOperationId(entry, usedOpIds, nextUcNum);
nextUcNum = extractUcNumber(operationId) + 1;
usedOpIds.add(operationId);
if (entry.getIntensity() > 0) {
newCount++;
}
}
}
} else {
// URI не найден в скрипте — ищем в прошлом профиле
String normalizedEntryUri = uriNormalizer.normalizeUri(entry.getUri(), projectType);
String oldKey = entry.getMethod().toUpperCase() + "|" + normalizedEntryUri;
String existingOpId = oldUriToOpId.get(oldKey);
if (existingOpId != null && !usedOpIds.contains(existingOpId)) {
operationId = existingOpId;
usedOpIds.add(operationId);
nextUcNum = Math.max(nextUcNum, extractUcNumber(operationId) + 1);
if (entry.getIntensity() > 0) {
log.info(" Восстановлен из прошлого профиля: {} {} → {} (инт: {})",
profileMethod, entry.getUri(), operationId, entry.getIntensity());
matchedCount++;
} else {
log.info(" Восстановлен ID из прошлого профиля для нулевой операции: {} {} → {} (инт: 0)",
profileMethod, entry.getUri(), operationId);
}
} else {
operationId = generateNewOperationId(entry, usedOpIds, nextUcNum);
nextUcNum = extractUcNumber(operationId) + 1;
usedOpIds.add(operationId);
if (entry.getIntensity() > 0) {
log.info(" Создан новый: {} {} → {} (инт: {})",
profileMethod, entry.getUri(), operationId, entry.getIntensity());
newCount++;
} else {
log.info(" Создан ID для нулевой операции: {} {} → {} (инт: 0)",
profileMethod, entry.getUri(), operationId);
}
}
}
int sla = DEFAULT_SLA;
if (lastProfileData.containsKey(operationId)) {
sla = lastProfileData.get(operationId).getSla();
}
String fullUri;
String uriFromProfile = entry.getUri();
if ("PPRB".equals(projectType)) {
// Для PPRB не добавляем префикс и не меняем URI
fullUri = uriFromProfile;
} else if (uriFromProfile.startsWith("/ic/ufs/corp-retail/") ||
uriFromProfile.startsWith("/ufs/mobile/")) {
fullUri = uriFromProfile;
} else if (uriFromProfile.startsWith("/")) {
fullUri = urlPrefix + uriFromProfile;
} else {
fullUri = urlPrefix + "/" + uriFromProfile;
}
CsvRow row = new CsvRow(
operationId,
fullUri,
entry.getMethod(),
entry.getIntensity(), // может быть 0
sla,
operationId,
requirement,
""
);
// ИЗМЕНЕНО: добавляем ВСЕ операции в список для Excel
allRowsForExcel.add(row);
// ИЗМЕНЕНО: в CSV добавляем ТОЛЬКО операции с интенсивностью > 0
if (entry.getIntensity() > 0) {
rowsForCsv.add(row);
log.info(" Добавлена в CSV и Excel: {} -> интенсивность={}", operationId, entry.getIntensity());
} else {
log.info(" Добавлена ТОЛЬКО в Excel (не в CSV): {} -> интенсивность=0", operationId);
}
}
// ========== СИНХРОНИЗАЦИЯ СТАТИКИ С UC29 ==========
if ("WEB".equals(projectType)) {
Optional<CsvRow> contactsRow = allRowsForExcel.stream()
.filter(row -> GET_CONTACTS_OPERATION_ID.equals(row.getName()))
.findFirst();
Optional<CsvRow> staticRow = allRowsForExcel.stream()
.filter(row -> STATIC_OPERATION_ID.equals(row.getName()))
.findFirst();
if (contactsRow.isPresent() && staticRow.isPresent()) {
int contactsIntensity = contactsRow.get().getIntensity();
CsvRow oldStaticRow = staticRow.get();
if (contactsIntensity != oldStaticRow.getIntensity()) {
CsvRow updatedStaticRow = new CsvRow(
oldStaticRow.getName(),
oldStaticRow.getUri(),
oldStaticRow.getMethod(),
contactsIntensity,
oldStaticRow.getSla(),
oldStaticRow.getTransactionName(),
oldStaticRow.getRequirement(),
oldStaticRow.getComment()
);
int index = allRowsForExcel.indexOf(oldStaticRow);
allRowsForExcel.set(index, updatedStaticRow);
// ИЗМЕНЕНО: также обновляем в rowsForCsv, если там есть
int csvIndex = rowsForCsv.indexOf(oldStaticRow);
if (csvIndex != -1) {
rowsForCsv.set(csvIndex, updatedStaticRow);
}
log.info(" Синхронизирована интенсивность: {} -> {} (как у {})",
STATIC_OPERATION_ID, contactsIntensity, GET_CONTACTS_OPERATION_ID);
}
}
}
// ========== 7. Сортировка ==========
// Сортировка для Excel: сначала операции из old profile, потом новые
List<CsvRow> rowsToSort;
boolean hasStatic = "WEB".equals(projectType) && allRowsForExcel.size() > 0 &&
allRowsForExcel.get(0).getName().equals(STATIC_OPERATION_ID);
if (hasStatic) {
rowsToSort = allRowsForExcel.subList(1, allRowsForExcel.size());
log.debug("Сортируем {} строк для Excel (без статики)", rowsToSort.size());
} else {
rowsToSort = allRowsForExcel;
log.debug("Сортируем все {} строк для Excel", rowsToSort.size());
}
// Разделяем на операции из old profile и новые
List<CsvRow> oldProfileRows = new ArrayList<>();
List<CsvRow> newRows = new ArrayList<>();
for (CsvRow row : rowsToSort) {
if (lastProfileData.containsKey(row.getName())) {
oldProfileRows.add(row);
} else {
newRows.add(row);
}
}
// Сортируем каждую группу по UC-номеру
Comparator<CsvRow> ucComparator = (row1, row2) -> {
int num1 = extractUcNumber(row1.getName());
int num2 = extractUcNumber(row2.getName());
return Integer.compare(num1, num2);
};
oldProfileRows.sort(ucComparator);
newRows.sort(ucComparator);
// Конкатенируем: сначала старые, потом новые
List<CsvRow> sortedRows = new ArrayList<>();
sortedRows.addAll(oldProfileRows);
sortedRows.addAll(newRows);
log.info(" Сортировка: {} операций из old profile, {} новых операций", oldProfileRows.size(), newRows.size());
List<CsvRow> finalRowsForExcel;
if (hasStatic) {
finalRowsForExcel = new ArrayList<>();
finalRowsForExcel.add(allRowsForExcel.get(0));
finalRowsForExcel.addAll(sortedRows);
} else {
finalRowsForExcel = sortedRows;
}
// ========== 8. Сортировка для CSV ==========
List<CsvRow> csvRowsToSort;
boolean hasStaticCsv = "WEB".equals(projectType) && rowsForCsv.size() > 0 &&
rowsForCsv.get(0).getName().equals(STATIC_OPERATION_ID);
if (hasStaticCsv) {
csvRowsToSort = rowsForCsv.subList(1, rowsForCsv.size());
log.debug("Сортируем {} строк для CSV (без статики)", csvRowsToSort.size());
} else {
csvRowsToSort = rowsForCsv;
log.debug("Сортируем все {} строк для CSV", csvRowsToSort.size());
}
csvRowsToSort.sort((row1, row2) -> {
int num1 = extractUcNumber(row1.getName());
int num2 = extractUcNumber(row2.getName());
return Integer.compare(num1, num2);
});
List<CsvRow> finalRowsForCsv;
if (hasStaticCsv) {
finalRowsForCsv = new ArrayList<>();
finalRowsForCsv.add(rowsForCsv.get(0));
finalRowsForCsv.addAll(csvRowsToSort);
} else {
finalRowsForCsv = csvRowsToSort;
}
// ========== 9. Сохранение CSV и возврат результата ==========
log.info("\n--- Статистика ---");
log.info(" Скрипт (операций): {} операций", scriptOperationsAfterRemoval);
log.info(" Профиль аналитика (инт > 0): {} операций", validProfileEntriesCount);
log.info(" Нужно удалить из скрипта: {} операций", extraInScript.size());
log.info(" Нужно добавить в скрипт: {} операций", extraInProfile.size());
log.info(" Найдено совпадений: {}", matchedCount);
log.info(" Создано новых ID: {}", newCount);
log.info(" Итого в CSV (intensity > 0): {} строк", finalRowsForCsv.size());
log.info(" Итого в Excel (все операции): {} строк", finalRowsForExcel.size());
saveCsv(finalRowsForCsv, outputFolder, outputFilename);
return finalRowsForExcel; }
private String generateNewOperationId(ProfileEntry entry, Set<String> usedOpIds, int nextUcNum) {
String method = entry.getMethod();
String uri = entry.getUri();
String suffix;
if ("MOBILE".equals(projectType)) {
String path = uri;
path = path.replaceFirst("^/ic/ufs/mobile/corp-retail/rest/", "");
path = path.replaceFirst("^/ufs/mobile/corp-retail/rest/", "");
path = path.replaceFirst("^/ufs/", "");
path = path.replaceFirst("^/business-analytics/", "");
path = path.replaceFirst("^/dictionaries/", "");
path = path.replaceFirst("^/", "");
suffix = path.replaceAll("/", "_");
suffix = suffix.replaceAll("-", "_");
suffix = suffix.replaceAll("[^a-zA-Z0-9_]", "_");
suffix = suffix.replaceAll("_+", "_");
suffix = suffix.replaceAll("^_|_$", "");
String methodPrefix = method.toUpperCase();
suffix = methodPrefix + "_" + suffix;
if (suffix.length() > 50) {
suffix = suffix.substring(0, 50);
}
while (true) {
String candidate = String.format("UC%02d_%s_WEB", nextUcNum, suffix);
if (!usedOpIds.contains(candidate)) {
return candidate;
}
nextUcNum++;
}
} else {
String pathAfterPrefix = uri;
if (uri.startsWith(urlPrefix)) {
pathAfterPrefix = uri.substring(urlPrefix.length());
}
if (pathAfterPrefix.startsWith("/")) {
pathAfterPrefix = pathAfterPrefix.substring(1);
}
if (pathAfterPrefix.endsWith("/")) {
pathAfterPrefix = pathAfterPrefix.substring(0, pathAfterPrefix.length() - 1);
}
suffix = pathAfterPrefix.replaceAll("/", "_");
suffix = suffix.replaceAll("-", "_");
suffix = suffix.replaceAll("[^a-zA-Z0-9_]", "_");
suffix = suffix.replaceAll("_+", "_");
suffix = suffix.replaceAll("^_|_$", "");
while (true) {
String candidate = String.format("UC%02d_%s_%s_WEB", nextUcNum, method, suffix);
if (!usedOpIds.contains(candidate)) {
return candidate;
}
nextUcNum++;
}
}
}
private int getNextUcNumber(Set<String> usedOpIds) {
if (usedOpIds == null || usedOpIds.isEmpty()) {
return 1;
}
int maxNum = 0;
for (String opId : usedOpIds) {
int num = extractUcNumber(opId);
if (num > maxNum && num < 1000) {
maxNum = num;
}
}
return maxNum + 1;
}
private int extractUcNumber(String operationId) {
if (operationId == null || !operationId.startsWith("UC")) {
return 0;
}
try {
int underscoreIndex = operationId.indexOf('_', 2);
if (underscoreIndex == -1) {
return 0;
}
return Integer.parseInt(operationId.substring(2, underscoreIndex));
} catch (Exception e) {
return 0;
}
}
private void saveCsv(List<CsvRow> rows, String outputFolder, String outputFilename) {
File folder = new File(outputFolder);
if (!folder.exists()) {
folder.mkdirs();
}
File outputFile = new File(folder, outputFilename);
try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile, StandardCharsets.UTF_8))) {
writer.println("Название;Интенсивность выполнения;Требование к времени отклика;Название транзакции;Требование;Комментарий");
for (CsvRow row : rows) {
writer.printf("%s;%d;%d;%s;%s;%s%n",
row.getName(),
row.getIntensity(),
row.getSla(),
row.getTransactionName(),
row.getRequirement(),
row.getComment()
);
}
log.info("\n Профиль сохранён: {}", outputFile.getAbsolutePath());
log.info(" Всего строк в CSV: {}", rows.size());
} catch (IOException e) {
log.error("Ошибка при сохранении CSV: {}", e.getMessage());
}
}
private String extractMethodFromOperationId(String operationId) {
if (operationId == null) return "GET";
if (operationId.contains("_POST_") || operationId.contains("POST_")) {
return "POST";
}
if (operationId.contains("_GET_") || operationId.contains("GET_")) {
return "GET";
}
String[] parts = operationId.split("_");
for (String part : parts) {
if (part.equalsIgnoreCase("POST")) {
return "POST";
}
if (part.equalsIgnoreCase("GET")) {
return "GET";
}
}
return "GET";
}
}
package org.example.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.example.model.CsvRow;
import org.example.model.OldProfileData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Slf4j
@Service
public class ExcelReportService {
@Autowired
private UriNormalizer uriNormalizer;
private static final String DOC_FOLDER = "/home/retb_test/PPRB_OTP/Tools/GeneratorProfile" + "/DocumentForTheScript";
private static final String TEMPLATE_FILE = "Анкета ЕФС web и mobile.xlsx";
private static final String STATIC_OPERATION_ID = "UC00_StaticJS_1_WEB";
private static final String GET_CONTACTS_OPERATION_ID = "UC29_GET_contacts_WEB";
// Индексы колонок для WEB (8 колонок)
private static final int COL_SCRIPT = 0;
private static final int COL_SERVICE_WEB = 1;
private static final int COL_TPH_WEB = 2;
private static final int COL_SLA_WEB = 3;
private static final int COL_LOGIN_WEB = 4;
private static final int COL_THREADS_WEB = 5;
private static final int COL_PACING_WEB = 6;
private static final int COL_ITERATIONS_WEB = 7;
// Индексы колонок для MOBILE (8 колонок)
private static final int COL_SCRIPT_MOB = 0;
private static final int COL_SERVICE_MOB = 1;
private static final int COL_TPH_MOB = 2;
private static final int COL_SLA_MOB = 3;
private static final int COL_LOGIN_MOB = 4;
private static final int COL_THREADS_MOB = 5;
private static final int COL_PACING_MOB = 6;
private static final int COL_ITERATIONS_MOB = 7;
public void generateExcelReport(List<CsvRow> newRows, String projectType,
Map<String, Integer> oldIntensityByService,
Set<String> oldServices,
Set<String> extraInScript,
Map<String, OldProfileData> lastProfileData) {
try {
File templateFile = new File(DOC_FOLDER, TEMPLATE_FILE);
String pagePrefix = "WEB".equals(projectType) ? "WEB" : "MOB";
Workbook workbook;
if (templateFile.exists()) {
workbook = new XSSFWorkbook(new FileInputStream(templateFile));
} else {
workbook = new XSSFWorkbook();
}
// Удаляем пустые страницы
for (int i = workbook.getNumberOfSheets() - 1; i >= 0; i--) {
String sheetName = workbook.getSheetName(i);
if (sheetName.startsWith(pagePrefix) && workbook.getSheetAt(i).getLastRowNum() == 0) {
workbook.removeSheetAt(i);
}
}
Sheet lastSheet = findLastSheetByPrefix(workbook, pagePrefix);
int nextReleaseNumber;
String newSheetName;
if (lastSheet == null) {
nextReleaseNumber = 5038;
newSheetName = pagePrefix + " 5.038";
} else {
int lastReleaseNumber = extractReleaseNumber(lastSheet.getSheetName());
nextReleaseNumber = lastReleaseNumber + 1;
newSheetName = pagePrefix + " " + formatReleaseNumber(nextReleaseNumber);
}
Sheet newSheet = workbook.createSheet(newSheetName);
if (lastSheet != null) {
copySheetStructure(lastSheet, newSheet);
removeDelayAndFixHeaders(newSheet, projectType);
removeHeaderRowIfExists(newSheet);
} else {
createHeaderRow(newSheet, projectType);
}
fillSheetWithData(newSheet, newRows, oldIntensityByService, oldServices, projectType, extraInScript, lastProfileData);
removeHeaderRowIfExists(newSheet);
try (FileOutputStream fos = new FileOutputStream(templateFile)) {
workbook.write(fos);
}
workbook.close();
log.info(" Excel отчёт обновлён: добавлена страница {}", newSheetName);
} catch (IOException e) {
log.error("Ошибка при создании Excel отчёта: {}", e.getMessage());
}
}
private void removeDelayAndFixHeaders(Sheet sheet, String projectType) {
Row oldHeaderRow = sheet.getRow(0);
if (oldHeaderRow != null) {
sheet.removeRow(oldHeaderRow);
}
Row newHeaderRow = sheet.createRow(0);
String[] headers;
if ("MOBILE".equals(projectType)) {
headers = new String[]{"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Логинов в час",
"Количество потоков", "Pacing", "Количество итераций"};
} else {
headers = new String[]{"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Логинов в час",
"Количество потоков", "Pacing", "Количество итераций"};
}
for (int i = 0; i < headers.length; i++) {
Cell cell = newHeaderRow.createCell(i);
cell.setCellValue(headers[i]);
sheet.setColumnWidth(i, (i == 7 || i == 8) ? 8000 : 5000);
}
Font headerFont = sheet.getWorkbook().createFont();
headerFont.setBold(true);
CellStyle headerStyle = sheet.getWorkbook().createCellStyle();
headerStyle.setFont(headerFont);
setBorders(headerStyle);
for (int i = 0; i < headers.length; i++) {
newHeaderRow.getCell(i).setCellStyle(headerStyle);
}
int maxCol = headers.length;
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row != null) {
for (int c = row.getLastCellNum() - 1; c >= maxCol; c--) {
Cell cell = row.getCell(c);
if (cell != null) {
row.removeCell(cell);
}
}
}
}
//log.info(" Заголовок пересоздан для проекта: {}", projectType);
}
private void removeHeaderRowIfExists(Sheet sheet) {
for (int r = sheet.getLastRowNum(); r >= 0; r--) {
Row row = sheet.getRow(r);
if (row == null) continue;
for (int c = 0; c < row.getLastCellNum(); c++) {
Cell cell = row.getCell(c);
if (cell != null) {
String cellValue = null;
try {
cellValue = cell.getStringCellValue();
} catch (IllegalStateException e) {
continue;
}
if (cellValue != null && cellValue.contains("Наименование операции")) {
sheet.shiftRows(r + 1, sheet.getLastRowNum(), -1);
log.info(" Удалена строка с содержимым: {}", cellValue);
break;
}
}
}
}
}
private Sheet findLastSheetByPrefix(Workbook workbook, String prefix) {
Sheet lastSheet = null;
int maxRelease = -1;
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
String sheetName = workbook.getSheetName(i);
if (sheetName.startsWith(prefix)) {
int releaseNum = extractReleaseNumber(sheetName);
if (releaseNum > maxRelease) {
maxRelease = releaseNum;
lastSheet = workbook.getSheetAt(i);
}
}
}
return lastSheet;
}
private int extractReleaseNumber(String sheetName) {
Pattern pattern = Pattern.compile("(\\d+)\\.(\\d+)");
Matcher matcher = pattern.matcher(sheetName);
if (matcher.find()) {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
return major * 1000 + minor;
}
return 0;
}
private String formatReleaseNumber(int releaseNumber) {
int major = releaseNumber / 1000;
int minor = releaseNumber % 1000;
return major + "." + String.format("%03d", minor);
}
private void createHeaderRow(Sheet sheet, String projectType) {
Row headerRow = sheet.createRow(0);
String[] headers;
if ("MOBILE".equals(projectType)) {
headers = new String[]{"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Логинов в час",
"Количество потоков", "Pacing", "Количество итераций"};
} else {
headers = new String[]{"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Логинов в час",
"Количество потоков", "Pacing", "Количество итераций"};
}
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
sheet.setColumnWidth(i, (i == 7 || i == 8) ? 8000 : 5000);
}
Font headerFont = sheet.getWorkbook().createFont();
headerFont.setBold(true);
CellStyle headerStyle = sheet.getWorkbook().createCellStyle();
headerStyle.setFont(headerFont);
setBorders(headerStyle);
for (int i = 0; i < headers.length; i++) {
headerRow.getCell(i).setCellStyle(headerStyle);
}
}
private void copySheetStructure(Sheet source, Sheet target) {
if (source.getRow(0) != null) {
for (int i = 0; i < source.getRow(0).getLastCellNum(); i++) {
target.setColumnWidth(i, source.getColumnWidth(i));
}
}
for (int r = 0; r <= source.getLastRowNum(); r++) {
Row sourceRow = source.getRow(r);
if (sourceRow != null) {
Row targetRow = target.createRow(r);
for (int c = 0; c < sourceRow.getLastCellNum(); c++) {
Cell sourceCell = sourceRow.getCell(c);
if (sourceCell != null) {
Cell targetCell = targetRow.createCell(c);
switch (sourceCell.getCellType()) {
case STRING:
targetCell.setCellValue(sourceCell.getStringCellValue());
break;
case NUMERIC:
targetCell.setCellValue(sourceCell.getNumericCellValue());
break;
case BOOLEAN:
targetCell.setCellValue(sourceCell.getBooleanCellValue());
break;
case FORMULA:
targetCell.setCellFormula(sourceCell.getCellFormula());
break;
default:
targetCell.setCellValue("");
}
CellStyle newStyle = target.getWorkbook().createCellStyle();
newStyle.cloneStyleFrom(sourceCell.getCellStyle());
targetCell.setCellStyle(newStyle);
}
}
}
}
}
private void fillSheetWithData(Sheet sheet, List<CsvRow> newRows,
Map<String, Integer> oldIntensityByService,
Set<String> oldServices,
String projectType,
Set<String> extraInScript,
Map<String, OldProfileData> lastProfileData) {
// ========== СИНХРОНИЗАЦИЯ СТАТИКИ С UC29 (ДЛЯ WEB) ==========
if ("WEB".equals(projectType)) {
CsvRow contactsRow = null;
CsvRow staticRow = null;
int contactsIndex = -1;
int staticIndex = -1;
// Ищем оба запроса
for (int i = 0; i < newRows.size(); i++) {
CsvRow row = newRows.get(i);
if (GET_CONTACTS_OPERATION_ID.equals(row.getName())) {
contactsRow = row;
contactsIndex = i;
} else if (STATIC_OPERATION_ID.equals(row.getName())) {
staticRow = row;
staticIndex = i;
}
}
// Если нашли оба - синхронизируем
if (contactsRow != null && staticRow != null) {
int contactsIntensity = contactsRow.getIntensity();
if (contactsIntensity != staticRow.getIntensity()) {
CsvRow updatedStaticRow = new CsvRow(
staticRow.getName(),
staticRow.getUri(),
staticRow.getMethod(),
contactsIntensity,
staticRow.getSla(),
staticRow.getTransactionName(),
staticRow.getRequirement(),
staticRow.getComment()
);
newRows.set(staticIndex, updatedStaticRow);
log.info(" Excel: синхронизирована интенсивность {} -> {} (как у {})",
STATIC_OPERATION_ID, contactsIntensity, GET_CONTACTS_OPERATION_ID);
}
}
}
// ========== ДЛЯ MOBILE: полная перезапись страницы ==========
if ("MOBILE".equals(projectType)) {
// Удаляем все строки кроме заголовка (строка 0)
for (int r = sheet.getLastRowNum(); r >= 1; r--) {
Row row = sheet.getRow(r);
if (row != null) {
sheet.removeRow(row);
}
}
// ПРИНУДИТЕЛЬНО удаляем строку 2, если она существует и пустая
Row row2 = sheet.getRow(2);
if (row2 != null) {
// Проверяем, пустая ли строка
boolean isEmpty = true;
for (int c = 0; c < row2.getLastCellNum(); c++) {
Cell cell = row2.getCell(c);
if (cell != null && cell.getCellType() != CellType.BLANK) {
isEmpty = false;
break;
}
}
if (isEmpty) {
// Сдвигаем строки начиная с 3 вверх, чтобы закрыть строку 2
if (sheet.getLastRowNum() >= 2) {
sheet.shiftRows(3, sheet.getLastRowNum(), -1);
}
}
}
log.info(" MOBILE страница очищена, будет создано {} новых строк", newRows.size());
Workbook workbook = sheet.getWorkbook();
CellStyle defaultStyle = workbook.createCellStyle();
setBorders(defaultStyle);
CellStyle goodStyle = workbook.createCellStyle();
goodStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
goodStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorders(goodStyle);
CellStyle badStyle = workbook.createCellStyle();
badStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
badStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorders(badStyle);
int currentRow = 1;
for (CsvRow csvRow : newRows) {
Row dataRow = sheet.createRow(currentRow);
String redKey = csvRow.getMethod() + "|" + csvRow.getUri();
// ДОБАВИТЬ ОТЛАДКУ ДЛЯ КАЖДОЙ ОПЕРАЦИИ С 0
if (csvRow.getIntensity() == 0) {
log.info("MOBILE: найдена операция с 0 - метод={}, uri={}, redKey={}",
csvRow.getMethod(), csvRow.getUri(), redKey);
log.info("MOBILE: extraInScript.contains(redKey) = {}",
extraInScript != null && extraInScript.contains(redKey));
log.info("MOBILE: oldServices.contains(serviceKey) = {}",
oldServices != null && oldServices.contains(
uriNormalizer.normalizeService(csvRow.getMethod(), csvRow.getUri(), projectType)));
}
boolean shouldBeRed = csvRow.getIntensity() == 0
&& extraInScript != null
&& extraInScript.contains(redKey);
if (csvRow.getIntensity() == 0) {
log.debug("MOBILE: shouldBeRed = {}", shouldBeRed);
}
if (shouldBeRed) {
log.info(" MOBILE: красим красным {} {} (интенсивность=0)",
csvRow.getMethod(), csvRow.getUri());
}
String serviceKey = uriNormalizer.normalizeService(csvRow.getMethod(), csvRow.getUri(), projectType);
boolean isNew = oldServices != null && !oldServices.contains(serviceKey)
&& (lastProfileData == null || !lastProfileData.containsKey(csvRow.getName()));
Integer oldTph = oldIntensityByService != null ? oldIntensityByService.get(serviceKey) : null;
boolean isIntensityChanged = oldTph != null && !oldTph.equals(csvRow.getIntensity()) && csvRow.getIntensity() > 0;
int currentTph = csvRow.getIntensity();
int iterations = shouldBeRed ? 1 : calculateIterations(currentTph);
int threads = shouldBeRed ? 1 : calculateThreads(currentTph);
// Определяем стиль для всей строки
CellStyle rowStyle;
if (shouldBeRed) {
rowStyle = badStyle;
} else if (isNew) {
rowStyle = goodStyle;
} else {
rowStyle = defaultStyle;
}
// Заполняем все ячейки
Cell cellScript = dataRow.createCell(COL_SCRIPT_MOB);
cellScript.setCellValue(csvRow.getName());
cellScript.setCellStyle(rowStyle);
Cell cellService = dataRow.createCell(COL_SERVICE_MOB);
cellService.setCellValue(csvRow.getMethod() + " " + csvRow.getUri());
cellService.setCellStyle(rowStyle);
Cell cellTph = dataRow.createCell(COL_TPH_MOB);
cellTph.setCellValue(currentTph);
cellTph.setCellStyle(rowStyle);
Cell cellSla = dataRow.createCell(COL_SLA_MOB);
cellSla.setCellValue(csvRow.getSla());
cellSla.setCellStyle(rowStyle);
Cell cellIterations = dataRow.createCell(COL_ITERATIONS_MOB);
cellIterations.setCellValue(iterations);
cellIterations.setCellStyle(rowStyle);
Cell cellThreads = dataRow.createCell(COL_THREADS_MOB);
cellThreads.setCellValue(threads);
cellThreads.setCellStyle(rowStyle);
// Логины в час - ФОРМУЛА
Cell cellLogin = dataRow.createCell(COL_LOGIN_MOB);
if (shouldBeRed) {
cellLogin.setCellValue(0);
} else {
String tphColLetter = getColumnLetter(COL_TPH_MOB);
String iterColLetter = getColumnLetter(COL_ITERATIONS_MOB);
String formula = String.format("ROUNDUP(%s%d/%s%d,0)",
tphColLetter, currentRow + 1, iterColLetter, currentRow + 1);
cellLogin.setCellFormula(formula);
}
cellLogin.setCellStyle(rowStyle);
Cell cellPacing = dataRow.createCell(COL_PACING_MOB);
if (shouldBeRed) {
cellPacing.setCellValue(0);
} else {
// Проверяем, нужно ли скорректировать итерации
int pacing = 0;
if (iterations > 0 && threads > 0) {
pacing = (int) Math.round(3600.0 / (((double) currentTph / iterations) / threads));
}
if (pacing > 300 && iterations > 1) {
// Список допустимых значений итераций (от меньшего к большему)
int[] validIterations = {1, 5, 10, 20, 40, 60};
int newIterations = iterations;
int newPacing = pacing;
// Ищем подходящее значение итераций
for (int i = validIterations.length - 1; i >= 0; i--) {
int candidateIterations = validIterations[i];
if (candidateIterations < iterations) {
int candidatePacing = (int) Math.round(3600.0 / (((double) currentTph / candidateIterations) / threads));
if (candidatePacing <= 300) {
newIterations = candidateIterations;
newPacing = candidatePacing;
break;
}
}
}
// Обновляем итерации в ячейке
Cell iterCell = dataRow.getCell(COL_ITERATIONS_MOB);
if (iterCell != null && newIterations != iterations) {
iterCell.setCellValue(newIterations);
log.debug(" MOBILE: скорректированы итерации: {} -> {} (пейсинг стал {})",
iterations, newIterations, newPacing);
}
}
// Ставим формулу с обновленными итерациями
String loginColLetter = getColumnLetter(COL_LOGIN_MOB);
String threadsColLetter = getColumnLetter(COL_THREADS_MOB);
String formula = String.format("ROUND(3600/(%s%d/%s%d),0)",
loginColLetter, currentRow + 1, threadsColLetter, currentRow + 1);
cellPacing.setCellFormula(formula);
}
cellPacing.setCellStyle(rowStyle);
// Если изменилась интенсивность (и не красный, и не новый) - красим только TPH зелёным
if (!shouldBeRed && !isNew && isIntensityChanged) {
cellTph.setCellStyle(goodStyle);
log.info(" MOBILE: изменилась интенсивность {} {}: {} -> {}",
csvRow.getMethod(), csvRow.getUri(), oldTph, currentTph);
}
currentRow++;
}
// Добавляем строку ИТОГО
int totalRowNum = currentRow;
Row totalRow = sheet.createRow(totalRowNum);
Cell totalLabel = totalRow.createCell(COL_SCRIPT_MOB);
totalLabel.setCellValue("ИТОГО");
totalLabel.setCellStyle(defaultStyle);
int lastDataRow = currentRow;
if (lastDataRow < 2) lastDataRow = 2;
String tphColLetter = getColumnLetter(COL_TPH_MOB);
String loginColLetter = getColumnLetter(COL_LOGIN_MOB);
String threadsColLetter = getColumnLetter(COL_THREADS_MOB);
Cell tphFormulaCell = totalRow.createCell(COL_TPH_MOB);
tphFormulaCell.setCellFormula(String.format("SUM(%s2:%s%d)", tphColLetter, tphColLetter, lastDataRow));
tphFormulaCell.setCellStyle(defaultStyle);
Cell loginFormulaCell = totalRow.createCell(COL_LOGIN_MOB);
loginFormulaCell.setCellFormula(String.format("SUM(%s2:%s%d)", loginColLetter, loginColLetter, lastDataRow));
loginFormulaCell.setCellStyle(defaultStyle);
Cell threadsFormulaCell = totalRow.createCell(COL_THREADS_MOB);
threadsFormulaCell.setCellFormula(String.format("SUM(%s2:%s%d)", threadsColLetter, threadsColLetter, lastDataRow));
threadsFormulaCell.setCellStyle(defaultStyle);
log.info(" MOBILE страница создана, {} операций", newRows.size());
return;
}
// ========== ДЛЯ WEB ==========
// Очищаем страницу перед заполнением (удаляем все строки кроме заголовка)
for (int r = sheet.getLastRowNum(); r >= 1; r--) {
Row row = sheet.getRow(r);
if (row != null) {
sheet.removeRow(row);
}
}
// ПРИНУДИТЕЛЬНО УДАЛЯЕМ СТРОКУ 2, ЕСЛИ ОНА ПУСТАЯ
Row row2 = sheet.getRow(2);
if (row2 != null) {
boolean isEmpty = true;
for (int c = 0; c < row2.getLastCellNum(); c++) {
Cell cell = row2.getCell(c);
if (cell != null && cell.getCellType() != CellType.BLANK) {
if (cell.getCellType() == CellType.STRING && !cell.getStringCellValue().isEmpty()) {
isEmpty = false;
break;
} else if (cell.getCellType() == CellType.NUMERIC) {
isEmpty = false;
break;
}
}
}
if (isEmpty && sheet.getLastRowNum() >= 3) {
sheet.shiftRows(3, sheet.getLastRowNum(), -1);
}
}
//log.info(" WEB страница очищена, будет создано {} новых строк", newRows.size());
Workbook workbook = sheet.getWorkbook();
CellStyle goodStyle = workbook.createCellStyle();
goodStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
goodStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorders(goodStyle);
CellStyle badStyle = workbook.createCellStyle();
badStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
badStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorders(badStyle);
CellStyle defaultStyle = workbook.createCellStyle();
setBorders(defaultStyle);
int colScript = COL_SCRIPT;
int colService = COL_SERVICE_WEB;
int colTph = COL_TPH_WEB;
int colSla = COL_SLA_WEB;
int colLogin = COL_LOGIN_WEB;
int colThreads = COL_THREADS_WEB;
int colPacing = COL_PACING_WEB;
int colIterations = COL_ITERATIONS_WEB;
// 1. Находим удалённые операции
Set<String> newServiceKeys = newRows.stream()
.map(row -> uriNormalizer.normalizeService(row.getMethod(), row.getUri(), projectType))
.collect(Collectors.toSet());
Set<String> deletedServiceKeys = new HashSet<>(oldServices);
deletedServiceKeys.removeAll(newServiceKeys);
// 2. Красим удалённые строки в красный
int maxCol = colIterations;
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row != null && colService >= 0) {
Cell serviceCell = row.getCell(colService);
if (serviceCell != null) {
try {
String fullService = serviceCell.getStringCellValue();
String method = fullService.substring(0, fullService.indexOf(' '));
String uri = fullService.substring(fullService.indexOf(' ') + 1);
String serviceKey = uriNormalizer.normalizeService(method, uri, projectType);
if (deletedServiceKeys.contains(serviceKey)) {
for (int c = 0; c <= maxCol; c++) {
Cell cell = row.getCell(c);
if (cell == null) cell = row.createCell(c);
cell.setCellStyle(badStyle);
}
log.info(" Покрашена (удалена из профиля): {}", serviceKey);
}
} catch (Exception e) {
log.warn(" Ошибка при обработке строки {}: {}", r, e.getMessage());
continue;
}
}
}
}
// 3. Обработка статики для WEB
CsvRow staticRow = null;
Iterator<CsvRow> iterator = newRows.iterator();
while (iterator.hasNext()) {
CsvRow row = iterator.next();
if ("UC00_StaticJS_1_WEB".equals(row.getName())) {
staticRow = row;
iterator.remove();
break;
}
}
if (staticRow != null) {
Integer staticRowNum = null;
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row != null && colScript >= 0) {
Cell scriptCell = row.getCell(colScript);
if (scriptCell != null) {
String scriptValue = null;
try {
scriptValue = scriptCell.getStringCellValue();
} catch (IllegalStateException e) {
continue;
}
if ("UC00_StaticJS_1_WEB".equals(scriptValue)) {
staticRowNum = r;
break;
}
}
}
}
Row staticRowExcel;
int staticRowIndex;
if (staticRowNum == null) {
int lastRowNum = sheet.getLastRowNum();
if (lastRowNum >= 1) {
sheet.shiftRows(1, lastRowNum, 1);
}
staticRowExcel = sheet.createRow(1);
staticRowIndex = 1;
staticRowExcel.createCell(colScript).setCellValue(staticRow.getName());
staticRowExcel.createCell(colService).setCellValue(staticRow.getMethod() + " " + staticRow.getUri());
log.info(" Добавлена строка со статическим запросом UC00_StaticJS_1_WEB");
} else {
staticRowExcel = sheet.getRow(staticRowNum);
staticRowIndex = staticRowNum;
log.info(" Обновлена статическая операция UC00_StaticJS_1_WEB");
}
String serviceKey = uriNormalizer.normalizeService(staticRow.getMethod(), staticRow.getUri(), projectType);
boolean isNew = oldServices != null && !oldServices.contains(serviceKey)
&& (lastProfileData == null || !lastProfileData.containsKey(staticRow.getName()));
Integer oldTph = oldIntensityByService != null ? oldIntensityByService.get(serviceKey) : null;
boolean isChanged = oldTph != null && !oldTph.equals(staticRow.getIntensity());
CellStyle staticStyle = (isNew || isChanged) ? goodStyle : defaultStyle;
if (isChanged) {
log.info(" Статика {} изменила интенсивность: {} -> {}",
STATIC_OPERATION_ID, oldTph, staticRow.getIntensity());
}
Cell tphCell = staticRowExcel.getCell(colTph);
if (tphCell == null) tphCell = staticRowExcel.createCell(colTph);
tphCell.setCellValue(staticRow.getIntensity());
tphCell.setCellStyle(staticStyle);
Cell slaCell = staticRowExcel.getCell(colSla);
if (slaCell == null) slaCell = staticRowExcel.createCell(colSla);
slaCell.setCellValue(staticRow.getSla());
slaCell.setCellStyle(defaultStyle);
int iterations = calculateIterations(staticRow.getIntensity());
Cell iterCell = staticRowExcel.getCell(colIterations);
if (iterCell == null) iterCell = staticRowExcel.createCell(colIterations);
iterCell.setCellValue(iterations);
iterCell.setCellStyle(defaultStyle);
int threads = calculateThreads(staticRow.getIntensity());
Cell threadsCell = staticRowExcel.getCell(colThreads);
if (threadsCell == null) threadsCell = staticRowExcel.createCell(colThreads);
threadsCell.setCellValue(threads);
threadsCell.setCellStyle(defaultStyle);
// Логины в час для статики = 0
Cell loginCell = staticRowExcel.getCell(colLogin);
if (loginCell == null) loginCell = staticRowExcel.createCell(colLogin);
loginCell.setCellValue(0);
loginCell.setCellStyle(defaultStyle);
// Pacing для статики - ФОРМУЛА =ОКРУГЛ(3600/(C2/F2);0)
Cell pacingCell = staticRowExcel.getCell(colPacing);
if (pacingCell == null) pacingCell = staticRowExcel.createCell(colPacing);
String tphColLetter = getColumnLetter(colTph);
String threadsColLetter = getColumnLetter(colThreads);
String pacingFormula = String.format("ROUND(3600/(%s%d/%s%d),0)",
tphColLetter, staticRowIndex + 1, threadsColLetter, staticRowIndex + 1);
pacingCell.setCellFormula(pacingFormula);
pacingCell.setCellStyle(defaultStyle);
}
// 4. Строим карту существующих строк по service
Map<String, Integer> existingRowsMap = new HashMap<>();
int maxRowNum = 1;
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row != null) {
Cell serviceCell = row.getCell(colService);
if (serviceCell != null) {
try {
String fullService = serviceCell.getStringCellValue();
String method = fullService.substring(0, fullService.indexOf(' '));
String uri = fullService.substring(fullService.indexOf(' ') + 1);
String serviceKey = uriNormalizer.normalizeService(method, uri, projectType);
existingRowsMap.put(serviceKey, r);
// Запоминаем максимальный индекс существующей строки
if (r > maxRowNum) {
maxRowNum = r;
}
} catch (Exception e) {
continue;
}
}
}
}
// 5. Удаляем старую строку ИТОГО
for (int r = sheet.getLastRowNum(); r >= 0; r--) {
Row row = sheet.getRow(r);
if (row != null) {
Cell firstCell = row.getCell(0);
if (firstCell != null) {
String value = null;
try {
value = firstCell.getStringCellValue();
} catch (IllegalStateException e) {
continue;
}
if ("ИТОГО".equals(value)) {
sheet.removeRow(row);
}
}
}
}
// 6. Обновляем существующие строки
Set<String> processedNames = new HashSet<>();
for (CsvRow row : newRows) {
String serviceKey = uriNormalizer.normalizeService(row.getMethod(), row.getUri(), projectType);
// ИЗМЕНЕНО: упрощённая логика - красный если интенсивность = 0
String redKey = row.getMethod() + "|" + row.getUri();
boolean shouldBeRed = row.getIntensity() == 0
&& extraInScript != null
&& extraInScript.contains(redKey);
if (shouldBeRed) {
log.info(" WEB: красим красным {} {} (интенсивность=0)",
row.getMethod(), row.getUri());
}
if (processedNames.contains(row.getName())) {
log.warn("⚠️ Пропущен дубликат операции в newRows: {}", row.getName());
continue;
}
boolean isNew = oldServices != null && !oldServices.contains(serviceKey)
&& (lastProfileData == null || !lastProfileData.containsKey(row.getName()));
Integer oldTph = oldIntensityByService != null ? oldIntensityByService.get(serviceKey) : null;
boolean isIntensityChanged = oldTph != null && !oldTph.equals(row.getIntensity()) && row.getIntensity() > 0;
Integer existingRowNum = existingRowsMap.get(serviceKey);
Row dataRow;
int currentRowNum;
if (existingRowNum != null) {
dataRow = sheet.getRow(existingRowNum);
currentRowNum = existingRowNum;
if (existingRowNum > maxRowNum) maxRowNum = existingRowNum;
} else {
maxRowNum++;
currentRowNum = maxRowNum;
dataRow = sheet.createRow(maxRowNum);
if (colScript >= 0) {
dataRow.createCell(colScript).setCellValue(row.getName());
}
dataRow.createCell(colService).setCellValue(row.getMethod() + " " + row.getUri());
}
processedNames.add(row.getName());
// ========== ЛОГИКА ПРИМЕНЕНИЯ СТИЛЕЙ ==========
// 1. Красные строки (вся строка красная)
if (shouldBeRed) {
for (int c = 0; c <= colIterations; c++) {
Cell cell = dataRow.getCell(c);
if (cell == null) cell = dataRow.createCell(c);
cell.setCellStyle(badStyle);
}
fillRowDataWeb(dataRow, row, currentRowNum, colScript, colService, colTph, colSla,
colIterations, colThreads, colLogin, colPacing, badStyle, false);
continue;
}
// 2. Новые операции (вся строка зелёная)
if (isNew) {
for (int c = 0; c <= colIterations; c++) {
Cell cell = dataRow.getCell(c);
if (cell == null) cell = dataRow.createCell(c);
cell.setCellStyle(goodStyle);
}
fillRowDataWeb(dataRow, row, currentRowNum, colScript, colService, colTph, colSla,
colIterations, colThreads, colLogin, colPacing, goodStyle, false);
continue;
}
// 3. Существующие операции (обычный стиль)
fillRowDataWeb(dataRow, row, currentRowNum, colScript, colService, colTph, colSla,
colIterations, colThreads, colLogin, colPacing, defaultStyle, false);
// 4. Если изменилась интенсивность - красим только TPH зелёным
if (isIntensityChanged) {
Cell tphCell = dataRow.getCell(colTph);
if (tphCell != null) {
tphCell.setCellStyle(goodStyle);
log.info(" Изменилась интенсивность: {} {}: {} -> {}",
row.getMethod(), row.getUri(), oldTph, row.getIntensity());
}
}
}
// Удаляем пустые строки
for (int r = sheet.getLastRowNum(); r > maxRowNum; r--) {
Row row = sheet.getRow(r);
if (row != null) {
sheet.removeRow(row);
}
}
// ИТОГО
int totalRowNum = maxRowNum + 1;
Row totalRow = sheet.createRow(totalRowNum);
int lastDataRow = maxRowNum;
if (lastDataRow < 2) lastDataRow = 2;
String tphColLetter = getColumnLetter(colTph);
String loginColLetter = getColumnLetter(colLogin);
String threadsColLetter = getColumnLetter(colThreads);
Cell tphFormulaCell = totalRow.createCell(colTph);
tphFormulaCell.setCellFormula("SUM(" + tphColLetter + "2:" + tphColLetter + (lastDataRow + 1) + ")");
tphFormulaCell.setCellStyle(defaultStyle);
Cell loginFormulaCell = totalRow.createCell(colLogin);
loginFormulaCell.setCellFormula("SUM(" + loginColLetter + "2:" + loginColLetter + (lastDataRow + 1) + ")");
loginFormulaCell.setCellStyle(defaultStyle);
Cell threadsFormulaCell = totalRow.createCell(colThreads);
threadsFormulaCell.setCellFormula("SUM(" + threadsColLetter + "2:" + threadsColLetter + (lastDataRow + 1) + ")");
threadsFormulaCell.setCellStyle(defaultStyle);
Cell totalLabel = totalRow.createCell(colScript >= 0 ? colScript : colService);
totalLabel.setCellValue("ИТОГО");
totalLabel.setCellStyle(defaultStyle);
}
private void fillRowDataWeb(Row dataRow, CsvRow row, int rowNum,
int colScript, int colService, int colTph, int colSla,
int colIterations, int colThreads, int colLogin, int colPacing,
CellStyle style, boolean isStatic) {
int currentTph = row.getIntensity();
int iterations = calculateIterations(currentTph);
int threads = calculateThreads(currentTph);
// Скрипт
if (colScript >= 0) {
Cell cell = dataRow.getCell(colScript);
if (cell == null) cell = dataRow.createCell(colScript);
cell.setCellValue(row.getName());
cell.setCellStyle(style);
}
// Вызываемый сервис
Cell serviceCell = dataRow.getCell(colService);
if (serviceCell == null) serviceCell = dataRow.createCell(colService);
serviceCell.setCellValue(row.getMethod() + " " + row.getUri());
serviceCell.setCellStyle(style);
// TPH
Cell tphCell = dataRow.getCell(colTph);
if (tphCell == null) tphCell = dataRow.createCell(colTph);
tphCell.setCellValue(currentTph);
tphCell.setCellStyle(style);
// SLA
Cell slaCell = dataRow.getCell(colSla);
if (slaCell == null) slaCell = dataRow.createCell(colSla);
slaCell.setCellValue(row.getSla());
slaCell.setCellStyle(style);
// Итерации
Cell iterCell = dataRow.getCell(colIterations);
if (iterCell == null) iterCell = dataRow.createCell(colIterations);
iterCell.setCellValue(iterations);
iterCell.setCellStyle(style);
// Потоки
Cell threadsCell = dataRow.getCell(colThreads);
if (threadsCell == null) threadsCell = dataRow.createCell(colThreads);
threadsCell.setCellValue(threads);
threadsCell.setCellStyle(style);
// Логины в час - ФОРМУЛА
Cell loginCell = dataRow.getCell(colLogin);
if (loginCell == null) loginCell = dataRow.createCell(colLogin);
if (isStatic) {
loginCell.setCellValue(0);
} else {
String tphColLetter = getColumnLetter(colTph);
String iterColLetter = getColumnLetter(colIterations);
String formula = String.format("ROUNDUP(%s%d/%s%d,0)",
tphColLetter, rowNum + 1, iterColLetter, rowNum + 1);
loginCell.setCellFormula(formula);
}
loginCell.setCellStyle(style);
Cell pacingCell = dataRow.getCell(colPacing);
if (pacingCell == null) pacingCell = dataRow.createCell(colPacing);
if (isStatic) {
String tphColLetter = getColumnLetter(colTph);
String threadsColLetter = getColumnLetter(colThreads);
String formula = String.format("ROUND(3600/(%s%d/%s%d),0)",
tphColLetter, rowNum + 1, threadsColLetter, rowNum + 1);
pacingCell.setCellFormula(formula);
} else {
// Проверяем, нужно ли скорректировать итерации
int pacing = (int) Math.round(3600.0 / (((double) currentTph / iterations) / threads));
if (pacing > 300 && iterations > 1) {
// Список допустимых значений итераций (от меньшего к большему)
int[] validIterations = {1, 5, 10, 20, 40, 60};
int newIterations = iterations;
int newPacing = pacing;
// Ищем подходящее значение итераций
for (int i = validIterations.length - 1; i >= 0; i--) {
int candidateIterations = validIterations[i];
if (candidateIterations < iterations) {
int candidatePacing = (int) Math.round(3600.0 / (((double) currentTph / candidateIterations) / threads));
if (candidatePacing <= 300) {
newIterations = candidateIterations;
newPacing = candidatePacing;
break;
}
}
}
if (iterCell != null && newIterations != iterations) {
iterCell.setCellValue(newIterations);
log.debug(" Скорректированы итерации: {} -> {} (пейсинг стал {})",
iterations, newIterations, newPacing);
}
// Используем скорректированные итерации для формулы
String loginColLetter = getColumnLetter(colLogin);
String threadsColLetter = getColumnLetter(colThreads);
String formula = String.format("ROUND(3600/(%s%d/%s%d),0)",
loginColLetter, rowNum + 1, threadsColLetter, rowNum + 1);
pacingCell.setCellFormula(formula);
} else {
// Пейсинг и так OK, просто ставим формулу
String loginColLetter = getColumnLetter(colLogin);
String threadsColLetter = getColumnLetter(colThreads);
String formula = String.format("ROUND(3600/(%s%d/%s%d),0)",
loginColLetter, rowNum + 1, threadsColLetter, rowNum + 1);
pacingCell.setCellFormula(formula);
}
}
pacingCell.setCellStyle(style);
}
private void setBorders(CellStyle style) {
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
}
private int calculateThreads(int tph) {
if (tph <= 100) return 3;
if (tph <= 200) return 6;
if (tph <= 500) return 8;
if (tph <= 1000) return 10;
if (tph <= 10000) return 20;
if (tph <= 15000) return 24;
if (tph <= 20000) return 28;
if (tph <= 25000) return 32;
if (tph <= 30000) return 36;
if (tph <= 40000) return 40;
if (tph <= 50000) return 44;
return 60;
}
private int calculateIterations(int tph) {
if (tph <= 900) return 1;
if (tph <= 1000) return 10;
if (tph <= 20000) return 20;
if (tph <= 40000) return 40;
return 60;
}
private String getColumnLetter(int colIndex) {
StringBuilder letter = new StringBuilder();
while (colIndex >= 0) {
letter.insert(0, (char) ('A' + (colIndex % 26)));
colIndex = colIndex / 26 - 1;
}
return letter.toString();
}
private String cleanUriFromParams(String uri) {
if (uri == null) return null;
String cleaned = uri.replaceAll("[\\p{Cf}]", "");
int queryIndex = cleaned.indexOf('?');
if (queryIndex != -1) {
return cleaned.substring(0, queryIndex);
}
return cleaned;
}
public void generatePprbExcelReport(List<CsvRow> newRows, String projectType,
Map<String, Integer> oldIntensityByService,
Set<String> oldServices,
Set<String> extraInScript,
Map<String, OldProfileData> lastProfileData) {
List<CsvRow> filteredRows = new ArrayList<>();
for (CsvRow row : newRows) {
String normalizedUri = uriNormalizer.normalizeUri(row.getUri(), "PPRB");
String redKey = row.getMethod() + "|" + normalizedUri;
// Операция с интенсивностью 0 И отсутствует в скрипте - пропускаем
boolean shouldSkip = row.getIntensity() == 0
&& extraInScript != null
&& !extraInScript.contains(redKey);
if (shouldSkip) {
log.info(" ППРБ: пропускаем операцию {} {} (интенсивность=0, нет в скрипте)",
row.getMethod(), row.getUri());
continue;
}
filteredRows.add(row);
}
try {
File templateFile = new File(DOC_FOLDER, TEMPLATE_FILE);
String pagePrefix = "PPRB";
Workbook workbook;
if (templateFile.exists()) {
workbook = new XSSFWorkbook(new FileInputStream(templateFile));
} else {
workbook = new XSSFWorkbook();
}
// Удаляем пустые страницы ППРБ
for (int i = workbook.getNumberOfSheets() - 1; i >= 0; i--) {
String sheetName = workbook.getSheetName(i);
if (sheetName.startsWith(pagePrefix) && workbook.getSheetAt(i).getLastRowNum() == 0) {
workbook.removeSheetAt(i);
}
}
Sheet lastSheet = findLastSheetByPrefix(workbook, pagePrefix);
int nextReleaseNumber;
String newSheetName;
if (lastSheet == null) {
nextReleaseNumber = 6026;
newSheetName = pagePrefix + " 6.026";
} else {
int lastReleaseNumber = extractReleaseNumber(lastSheet.getSheetName());
nextReleaseNumber = lastReleaseNumber + 1;
newSheetName = pagePrefix + " " + formatReleaseNumber(nextReleaseNumber);
}
Sheet newSheet = workbook.createSheet(newSheetName);
if (lastSheet != null) {
copySheetStructure(lastSheet, newSheet);
removeDelayAndFixHeadersPprb(newSheet);
removeHeaderRowIfExists(newSheet);
} else {
createHeaderRowPprb(newSheet);
}
fillSheetWithDataPprb(newSheet, filteredRows, oldIntensityByService, oldServices, extraInScript);
removeHeaderRowIfExists(newSheet);
try (FileOutputStream fos = new FileOutputStream(templateFile)) {
workbook.write(fos);
}
workbook.close();
log.info(" ППРБ Excel отчёт обновлён: добавлена страница {}", newSheetName);
} catch (IOException e) {
log.error("Ошибка при создании ППРБ Excel отчёта: {}", e.getMessage());
}
}
private void createHeaderRowPprb(Sheet sheet) {
Row headerRow = sheet.createRow(0);
String[] headers = {"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Количество потоков", "Pacing"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
sheet.setColumnWidth(i, 5000);
}
Font headerFont = sheet.getWorkbook().createFont();
headerFont.setBold(true);
CellStyle headerStyle = sheet.getWorkbook().createCellStyle();
headerStyle.setFont(headerFont);
setBorders(headerStyle);
for (int i = 0; i < headers.length; i++) {
headerRow.getCell(i).setCellStyle(headerStyle);
}
}
private void removeDelayAndFixHeadersPprb(Sheet sheet) {
Row oldHeaderRow = sheet.getRow(0);
if (oldHeaderRow != null) {
sheet.removeRow(oldHeaderRow);
}
createHeaderRowPprb(sheet);
}
private void fillSheetWithDataPprb(Sheet sheet, List<CsvRow> newRows,
Map<String, Integer> oldIntensityByService,
Set<String> oldServices,
Set<String> extraInScript) {
// Очищаем страницу перед заполнением
for (int r = sheet.getLastRowNum(); r >= 1; r--) {
Row row = sheet.getRow(r);
if (row != null) {
sheet.removeRow(row);
}
}
// ПРИНУДИТЕЛЬНО УДАЛЯЕМ СТРОКУ 2, ЕСЛИ ОНА ПУСТАЯ
Row row2 = sheet.getRow(2);
if (row2 != null) {
boolean isEmpty = true;
for (int c = 0; c < row2.getLastCellNum(); c++) {
Cell cell = row2.getCell(c);
if (cell != null && cell.getCellType() != CellType.BLANK) {
if (cell.getCellType() == CellType.STRING && !cell.getStringCellValue().isEmpty()) {
isEmpty = false;
break;
} else if (cell.getCellType() == CellType.NUMERIC) {
isEmpty = false;
break;
}
}
}
if (isEmpty && sheet.getLastRowNum() >= 3) {
sheet.shiftRows(3, sheet.getLastRowNum(), -1);
}
}
log.info(" ППРБ страница очищена, будет создано {} новых строк", newRows.size());
Workbook workbook = sheet.getWorkbook();
CellStyle goodStyle = workbook.createCellStyle();
goodStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
goodStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorders(goodStyle);
CellStyle badStyle = workbook.createCellStyle();
badStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
badStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorders(badStyle);
CellStyle defaultStyle = workbook.createCellStyle();
setBorders(defaultStyle);
// Индексы колонок для ППРБ
int colScript = 0;
int colService = 1;
int colTph = 2;
int colSla = 3;
int colThreads = 4;
int colPacing = 5;
int currentRow = 1;
for (CsvRow row : newRows) {
String normalizedUri = uriNormalizer.normalizeUri(row.getUri(), "PPRB");
String serviceKey = uriNormalizer.normalizeService(row.getMethod(), row.getUri(), "PPRB");
String redKey = row.getMethod() + "|" + normalizedUri;
boolean shouldBeRed = row.getIntensity() == 0
&& extraInScript != null
&& extraInScript.contains(redKey);
boolean isNew = (oldServices == null || !oldServices.contains(serviceKey))
&& (lastProfileData == null || !lastProfileData.containsKey(row.getName()));
Integer oldTph = oldIntensityByService != null ? oldIntensityByService.get(serviceKey) : null;
boolean isIntensityChanged = oldTph != null && !oldTph.equals(row.getIntensity()) && row.getIntensity() > 0;
Row dataRow = sheet.createRow(currentRow);
// Определяем стиль для всей строки
CellStyle rowStyle;
if (shouldBeRed) {
rowStyle = badStyle;
log.info(" ППРБ: красим красным {} {} (интенсивность=0)",
row.getMethod(), row.getUri());
} else if (isNew) {
rowStyle = goodStyle;
log.info(" ППРБ: новая операция {} {}", row.getMethod(), row.getUri());
} else {
rowStyle = defaultStyle;
}
int currentTph = row.getIntensity();
int threads = calculateThreads(currentTph);
// Скрипт
Cell cellScript = dataRow.createCell(colScript);
cellScript.setCellValue(row.getName());
cellScript.setCellStyle(rowStyle);
// Вызываемый сервис
Cell cellService = dataRow.createCell(colService);
cellService.setCellValue(row.getMethod() + " " + row.getUri());
cellService.setCellStyle(rowStyle);
// TPH
Cell cellTph = dataRow.createCell(colTph);
cellTph.setCellValue(currentTph);
cellTph.setCellStyle(rowStyle);
// SLA
Cell cellSla = dataRow.createCell(colSla);
cellSla.setCellValue(row.getSla());
cellSla.setCellStyle(rowStyle);
// Количество потоков
Cell cellThreads = dataRow.createCell(colThreads);
cellThreads.setCellValue(threads);
cellThreads.setCellStyle(rowStyle);
Cell cellPacing = dataRow.createCell(colPacing);
if (shouldBeRed) {
cellPacing.setCellValue(0);
} else {
String tphColLetter = getColumnLetter(colTph);
String threadsColLetter = getColumnLetter(colThreads);
// Формула с MIN - не даст пейсингу быть больше 300
String formula = String.format("MIN(ROUND(3600/(%s%d/%s%d),0), 300)",
tphColLetter, currentRow + 1, threadsColLetter, currentRow + 1);
cellPacing.setCellFormula(formula);
}
cellPacing.setCellStyle(rowStyle);
// Если изменилась интенсивность - красим только TPH зелёным
if (!shouldBeRed && !isNew && isIntensityChanged) {
cellTph.setCellStyle(goodStyle);
log.info(" ППРБ: изменилась интенсивность {} {}: {} -> {}",
row.getMethod(), row.getUri(), oldTph, currentTph);
}
currentRow++;
}
// Добавляем строку ИТОГО
int totalRowNum = currentRow;
Row totalRow = sheet.createRow(totalRowNum);
Cell totalLabel = totalRow.createCell(colScript);
totalLabel.setCellValue("ИТОГО");
totalLabel.setCellStyle(defaultStyle);
int lastDataRow = currentRow;
if (lastDataRow < 2) lastDataRow = 2;
String tphColLetter = getColumnLetter(colTph);
String threadsColLetter = getColumnLetter(colThreads);
Cell tphFormulaCell = totalRow.createCell(colTph);
tphFormulaCell.setCellFormula("SUM(" + tphColLetter + "2:" + tphColLetter + lastDataRow + ")");
tphFormulaCell.setCellStyle(defaultStyle);
Cell threadsFormulaCell = totalRow.createCell(colThreads);
threadsFormulaCell.setCellFormula("SUM(" + threadsColLetter + "2:" + threadsColLetter + lastDataRow + ")");
threadsFormulaCell.setCellStyle(defaultStyle);
log.info(" ППРБ страница создана, {} операций", newRows.size());
}
}
package org.example.service;
import lombok.extern.slf4j.Slf4j;
import org.example.model.CsvRow;
import org.example.model.OldProfileData;
import org.example.model.ProfileEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
public class GeneratorService {
@Autowired
private FileScanService fileScanService;
@Autowired
private ScriptParserService scriptParserService;
@Autowired
private ProfileAnalystParserService profileAnalystParserService;
@Autowired
private LastProfileReaderService lastProfileReaderService;
@Autowired
private CsvGeneratorService csvGeneratorService;
@Autowired
private ExcelReportService excelReportService;
@Autowired
private UriNormalizer uriNormalizer;
public void generate(String projectType) {
log.info("\n========================================");
log.info(" ГЕНЕРАТОР ПРОФИЛЯ НАГРУЗОЧНОГО ТЕСТИРОВАНИЯ");
log.info(" Проект: {}", projectType);
log.info("========================================\n");
String profileAnalystFolder = fileScanService.getProfileAnalystFolder(projectType);
String scriptFolder = fileScanService.getScriptFolder(projectType);
String lastProfileFolder = fileScanService.getLastProfileFolder(projectType);
String outputFolder = fileScanService.getOutputFolder(projectType);
String outputFilename = fileScanService.getOutputFilename(projectType);
String urlPrefix = fileScanService.getUrlPrefix(projectType);
String requirement = fileScanService.getRequirement(projectType);
fileScanService.ensureFolderExists(profileAnalystFolder);
fileScanService.ensureFolderExists(scriptFolder);
fileScanService.ensureFolderExists(lastProfileFolder);
fileScanService.ensureFolderExists(outputFolder);
log.info(" Поиск JSON скрипта в папке: {}", scriptFolder);
File scriptFile = fileScanService.findFirstFileByExtension(scriptFolder, ".json");
if (scriptFile == null) {
log.error(" JSON скрипт не найден! Завершение работы.");
return;
}
Map<String, List<String>> urlToOpIds = scriptParserService.parseScriptAndBuildUrlToOpIdMap(scriptFile, urlPrefix);
log.info("\n Поиск CSV профиля аналитика в папке: {}", profileAnalystFolder);
File profileAnalystFile = fileScanService.findFirstFileByExtension(profileAnalystFolder, ".csv");
if (profileAnalystFile == null) {
log.error(" CSV профиль аналитика не найден! Завершение работы.");
return;
}
List<ProfileEntry> profileEntries = profileAnalystParserService.parseProfileAnalyst(profileAnalystFile, projectType);
if (profileEntries.isEmpty()) {
log.error(" Профиль аналитика пуст! Завершение работы.");
return;
}
log.info("\n Поиск прошлого профиля в папке: {}", lastProfileFolder);
File lastProfileFile = fileScanService.findFirstFileByExtension(lastProfileFolder, ".xlsx");
// Загружаем данные из прошлого профиля
Map<String, OldProfileData> lastProfileData = lastProfileReaderService.loadLastProfile(lastProfileFile);
log.info("");
// Генерируем профиль (CSV уже сохраняется внутри этого метода)
List<CsvRow> rows = csvGeneratorService.generateProfile(urlToOpIds, profileEntries, lastProfileData,
outputFolder, outputFilename, requirement, urlPrefix, projectType);
Set<String> extraInScript = csvGeneratorService.getLastExtraInScript();
// Логируем для контроля
if (!extraInScript.isEmpty()) {
log.info("\n Передаём в ExcelReportService {} операций для покраски красным:", extraInScript.size());
for (String key : extraInScript) {
log.info(" {}", key);
}
}
if ("WEB".equals(projectType) || "MOBILE".equals(projectType)) {
// Существующая логика для WEB и MOBILE
generateWebMobileReport(projectType, rows, lastProfileData, extraInScript, outputFolder, outputFilename);
} else if ("PPRB".equals(projectType)) {
// Новая логика для ППРБ
generatePprbReport(projectType, rows, lastProfileData, extraInScript, outputFolder, outputFilename);
}
log.info("\n========================================");
log.info(" РАБОТА ЗАВЕРШЕНА");
log.info("========================================");
}
private void generateWebMobileReport(String projectType, List<CsvRow> rows,
Map<String, OldProfileData> lastProfileData,
Set<String> extraInScript,
String outputFolder, String outputFilename) {
// Загружаем старые данные
Map<String, Integer> oldIntensityByService = new HashMap<>();
Set<String> oldServices = new HashSet<>();
log.info("=== Загрузка данных из прошлого профиля ===");
for (Map.Entry<String, OldProfileData> entry : lastProfileData.entrySet()) {
String opName = entry.getKey();
OldProfileData data = entry.getValue();
if (data.getFullService() != null && !data.getFullService().isEmpty()) {
String fullService = data.getFullService();
String method;
String uri;
if (fullService.contains(" ")) {
method = fullService.substring(0, fullService.indexOf(' '));
uri = fullService.substring(fullService.indexOf(' ') + 1);
} else {
log.warn(" ⚠️ Не удалось разделить fullService: {}", fullService);
continue;
}
String serviceKey = uriNormalizer.normalizeService(method, uri, projectType);
oldIntensityByService.put(serviceKey, data.getIntensity());
oldServices.add(serviceKey);
log.info(" Загружен: {} -> интенсивность: {}", serviceKey, data.getIntensity());
} else {
log.warn(" ⚠️ Нет fullService для операции: {}, пробуем восстановить из operationId", opName);
String method = extractMethodFromOperationId(opName);
String uri = extractUriFromOperationId(opName);
if (uri != null && !uri.isEmpty()) {
String normalizedUri = uriNormalizer.normalizeUri(uri, projectType);
String serviceKey = method + " " + normalizedUri;
oldIntensityByService.put(serviceKey, data.getIntensity());
oldServices.add(serviceKey);
log.info(" Восстановлен: {} -> интенсивность: {}", serviceKey, data.getIntensity());
}
}
}
// Разделяем строки для Excel
List<CsvRow> rowsForExcel = new ArrayList<>();
for (CsvRow row : rows) {
if (row.getIntensity() > 0) {
rowsForExcel.add(row);
} else {
String serviceKey = uriNormalizer.normalizeService(row.getMethod(), row.getUri(), projectType);
if (oldServices.contains(serviceKey)) {
rowsForExcel.add(row);
log.info(" Операция с интенсивностью 0, но была в прошлом профиле: {} {} -> будет красной в Excel",
row.getMethod(), row.getUri());
}
else {
log.debug(" Пропущена (интенсивность 0 и нет в прошлом профиле): {} {}",
row.getMethod(), row.getUri());
}
}
}
// Отправляем в Excel
excelReportService.generateExcelReport(rowsForExcel, projectType, oldIntensityByService, oldServices, extraInScript, lastProfileData);
}
private void generatePprbReport(String projectType, List<CsvRow> rows,
Map<String, OldProfileData> lastProfileData,
Set<String> extraInScript,
String outputFolder, String outputFilename) {
// Загружаем старые данные (если есть)
Map<String, Integer> oldIntensityByService = new HashMap<>();
Set<String> oldServices = new HashSet<>();
if (lastProfileData != null && !lastProfileData.isEmpty()) {
log.info("=== Загрузка данных из прошлого профиля ППРБ ===");
for (Map.Entry<String, OldProfileData> entry : lastProfileData.entrySet()) {
OldProfileData data = entry.getValue();
if (data.getFullService() != null && !data.getFullService().isEmpty()) {
String fullService = data.getFullService();
if (fullService.contains(" ")) {
String method = fullService.substring(0, fullService.indexOf(' '));
String uri = fullService.substring(fullService.indexOf(' ') + 1);
// ОТЛАДКА: что пришло из прошлого профиля
log.info(" raw: method='{}', uri='{}'", method, uri);
String serviceKey = uriNormalizer.normalizeService(method, uri, projectType);
// ОТЛАДКА: что получилось после нормализации
log.info(" normalized: serviceKey='{}'", serviceKey);
oldIntensityByService.put(serviceKey, data.getIntensity());
oldServices.add(serviceKey);
}
}
}
}
List<CsvRow> rowsForExcel = new ArrayList<>(rows);
excelReportService.generatePprbExcelReport(rowsForExcel, projectType, oldIntensityByService, oldServices, extraInScript, lastProfileData);
}
private String extractMethodFromOperationId(String operationId) {
if (operationId == null) return "GET";
if (operationId.contains("POST")) return "POST";
if (operationId.contains("GET")) return "GET";
String[] parts = operationId.split("_");
for (String part : parts) {
if (part.equalsIgnoreCase("POST") || part.equalsIgnoreCase("GET")) {
return part.toUpperCase();
}
}
return "GET";
}
private String extractUriFromOperationId(String operationId) {
if (operationId == null || !operationId.contains("_")) {
return operationId;
}
String withoutUc = operationId.substring(operationId.indexOf('_') + 1);
withoutUc = withoutUc.replaceFirst("^POST_", "");
withoutUc = withoutUc.replaceFirst("^GET_", "");
withoutUc = withoutUc.replace("_WEB", "");
String uri = "/ic/ufs/corp-retail/rest/" + withoutUc.replace("_", "/");
return uri;
}
}