Загрузка данных
package org.example.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "app.folders")
public class AppConfig {
private String profileAnalyst;
private String script;
private String lastProfile;
private String output;
}
package org.example.controller;
import org.example.service.GeneratorService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
@RestController
@RequestMapping("/api/generator")
@CrossOrigin(origins = "*")
public class GeneratorController {
@Autowired
private GeneratorService generatorService;
private final AtomicBoolean isGenerating = new AtomicBoolean(false);
@PostMapping("/generate")
public Map<String, String> generateProfile(@RequestBody Map<String, String> request) {
String projectNumber = request.get("projectNumber");
if (projectNumber == null || projectNumber.isEmpty()) {
return Map.of("error", "projectNumber не указан");
}
if (isGenerating.get()) {
return Map.of("error", "Генерация уже выполняется");
}
String projectType = convertProjectNumberToType(projectNumber);
if (projectType == null) {
return Map.of("error", "Неверный номер проекта");
}
log.info("Запуск генерации для проекта: {}", projectType);
CompletableFuture.runAsync(() -> {
try {
isGenerating.set(true);
generatorService.generate(projectType); // вызываем твой сервис
log.info("Генерация завершена");
} catch (Exception e) {
log.error("Ошибка: {}", e.getMessage());
} finally {
isGenerating.set(false);
}
});
return Map.of("status", "success", "message", "Генерация запущена");
}
@GetMapping("/status")
public Map<String, Object> getStatus() {
return Map.of("isGenerating", isGenerating.get());
}
private String convertProjectNumberToType(String projectNumber) {
switch (projectNumber) {
case "1": return "WEB";
case "2": return "MOBILE";
case "3": return "PPRB";
default: return null;
}
}
}
package org.example.model;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class CsvRow {
private String name;
private String uri;
private String method;
private int intensity;
private int sla;
private String transactionName;
private String requirement;
private String comment;
}
package org.example.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OldProfileData {
private String fullService; // полный "Вызываемый сервис" (метод + URI)
private int intensity;
private int sla;
}
package org.example.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProfileData {
private int intensity;
private int sla;
}
package org.example.model;
import lombok.Data;
@Data
public class ProfileEntry {
private String uri;
private String method;
private int intensity;
}
package org.example.model;
import lombok.Data;
import java.util.List;
@Data
public class ScriptOperation {
private String operation_id;
private String url;
private String method;
private Integer intensity;
private List<ScriptOperation> child_operations;
}
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;
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 {
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 {
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
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());
}
rowsToSort.sort((row1, row2) -> {
int num1 = extractUcNumber(row1.getName());
int num2 = extractUcNumber(row2.getName());
return Integer.compare(num1, num2);
});
List<CsvRow> finalRowsForExcel;
if (hasStatic) {
finalRowsForExcel = new ArrayList<>();
finalRowsForExcel.add(allRowsForExcel.get(0));
finalRowsForExcel.addAll(rowsToSort);
} else {
finalRowsForExcel = rowsToSort;
}
// ========== 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.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) {
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);
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) {
// ========== СИНХРОНИЗАЦИЯ СТАТИКИ С 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);
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);
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);
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) {
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);
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.springframework.stereotype.Service;
import java.io.File;
@Slf4j
@Service
public class FileScanService {
// Базовые папки
private static final String BASE_PROFILE_ANALYST_FOLDER = "/home/retb_test/PPRB_OTP/Tools/GeneratorProfile" + "/profile_prom";
private static final String BASE_SCRIPT_FOLDER = "/home/retb_test/PPRB_OTP/Tools/GeneratorProfile" + "/script";
private static final String BASE_LAST_PROFILE_FOLDER = "/home/retb_test/PPRB_OTP/Tools/GeneratorProfile" + "/profile_last";
private static final String BASE_OUTPUT_FOLDER = "/home/retb_test/PPRB_OTP/Tools/GeneratorProfile" + "/profile_current";
public String getProfileAnalystFolder(String projectType) {
return BASE_PROFILE_ANALYST_FOLDER + "/" + getProjectFolderName(projectType);
}
public String getScriptFolder(String projectType) {
return BASE_SCRIPT_FOLDER + "/" + getProjectFolderName(projectType);
}
public String getLastProfileFolder(String projectType) {
return BASE_LAST_PROFILE_FOLDER + "/" + getProjectFolderName(projectType);
}
public String getOutputFolder(String projectType) {
return BASE_OUTPUT_FOLDER + "/" + getProjectFolderName(projectType);
}
private String getProjectFolderName(String projectType) {
switch (projectType) {
case "WEB":
return "EFS_Web";
case "MOBILE":
return "EFS_Mobile";
case "PPRB":
return "PPRB";
default:
return "EFS_Web";
}
}
public String getOutputFilename(String projectType) {
switch (projectType) {
case "WEB":
return "BusinessApp_EFS_Web_Profile.csv";
case "MOBILE":
return "BusinessApp_EFS_Mobile_Profile.csv";
case "PPRB":
return "BusinessApp_PPRB_Profile.csv";
default:
return "BusinessApp_EFS_Web_Profile.csv";
}
}
public String getUrlPrefix(String projectType) {
switch (projectType) {
case "WEB":
return "/ic/ufs/corp-retail/";
case "MOBILE":
return "/ufs/mobile/corp-retail/rest/";
case "PPRB":
return "/api/";
default:
return "/ic/ufs/corp-retail/";
}
}
public String getRequirement(String projectType) {
switch (projectType) {
case "WEB":
return "BusinessApp_EFS_Web_main";
case "MOBILE":
return "BussinesApp_EFS_Mobile_main";
case "PPRB":
return "BusinessApp_PPRB_main";
default:
return "BusinessApp_EFS_Web_main";
}
}
public File findFirstFileByExtension(String folderPath, String extension) {
File folder = new File(folderPath);
if (!folder.exists() || !folder.isDirectory()) {
log.error("Папка не найдена: {}", folderPath);
return null;
}
File[] files = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(extension.toLowerCase()));
if (files == null || files.length == 0) {
log.warn("Файлов с расширением {} не найдено в папке: {}", extension, folderPath);
return null;
}
File found = files[0];
log.info("Найден файл: {}", found.getName());
return found;
}
public boolean ensureFolderExists(String folderPath) {
File folder = new File(folderPath);
if (!folder.exists()) {
log.info("Создаю папку: {}", folderPath);
return folder.mkdirs();
}
return true;
}
}
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);
}
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);
}
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;
}
}
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.OldProfileData;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class LastProfileReaderService {
private static final int DEFAULT_SLA = 3;
private static final int DEFAULT_INTENSITY = 300000;
public Map<String, OldProfileData> loadLastProfile(File lastProfileFile) {
Map<String, OldProfileData> operationToData = new HashMap<>();
log.info("=== ЗАГРУЗКА ПРОШЛОГО ПРОФИЛЯ ===");
log.info("Файл: {}", lastProfileFile != null ? lastProfileFile.getAbsolutePath() : "null");
if (lastProfileFile == null || !lastProfileFile.exists()) {
log.warn("Файл прошлого профиля не найден");
return operationToData;
}
log.info("Файл существует, размер: {} байт", lastProfileFile.length());
try (FileInputStream fis = new FileInputStream(lastProfileFile);
Workbook workbook = new XSSFWorkbook(fis)) {
//log.info("Количество листов в файле: {}", workbook.getNumberOfSheets());
Sheet sheet = workbook.getSheetAt(0);
//log.info("Первый лист: {}", sheet.getSheetName());
//log.info("Последняя строка: {}", sheet.getLastRowNum());
Row headerRow = null;
int headerRowNum = -1;
for (int r = 0; r <= 5; r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
// Ищем в любой колонке строки
for (int i = 0; i < row.getLastCellNum(); i++) {
Cell cell = row.getCell(i);
if (cell != null) {
String cellValue = getCellValueAsString(cell);
if (cellValue != null && (cellValue.contains("Наименование операции") ||
cellValue.contains("Скрипт") ||
cellValue.contains("Вызываемый сервис"))) {
headerRow = row;
headerRowNum = r;
break;
}
}
}
if (headerRow != null) break;
}
if (headerRow == null) {
log.error("Заголовок не найден");
return operationToData;
}
//log.info("Заголовки найдены в строке {}", headerRowNum);
// Выводим заголовки
/* log.info("Заголовки колонок:");
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
Cell cell = headerRow.getCell(i);
String value = cell != null ? getCellValueAsString(cell) : "null";
log.info(" Колонка {}: '{}'", i, value);
} */
// Динамически определяем индексы колонок
int colService = findColumn(headerRow, "Вызываемый сервис");
int colIntensity = findColumn(headerRow, "TPH");
int colSla = findColumn(headerRow, "SLA");
int colOperationName = findColumn(headerRow, "Скрипт");
log.info("Найдены колонки: service={}, intensity={}, sla={}, operationName={}",
colService, colIntensity, colSla, colOperationName);
if (colService == -1) {
log.warn("Колонка 'Вызываемый сервис' не найдена");
}
int rowCount = 0;
int skippedCount = 0;
int rowNumber = 0;
for (Row row : sheet) {
rowNumber++;
if (rowNumber == 1) {
continue; // пропускаем заголовок
}
// Получаем полный "Вызываемый сервис" (может быть null)
String fullService = null;
if (colService != -1) {
Cell serviceCell = row.getCell(colService);
if (serviceCell != null) {
fullService = getCellValueAsString(serviceCell);
}
}
// Получаем интенсивность
int intensity = DEFAULT_INTENSITY;
if (colIntensity != -1) {
Cell intensityCell = row.getCell(colIntensity);
if (intensityCell != null) {
Integer parsedIntensity = getCellValueAsInteger(intensityCell);
if (parsedIntensity != null) {
intensity = parsedIntensity;
}
}
}
// Получаем SLA
int sla = DEFAULT_SLA;
if (colSla != -1) {
Cell slaCell = row.getCell(colSla);
if (slaCell != null) {
Integer parsedSla = getCellValueAsInteger(slaCell);
if (parsedSla != null) {
sla = parsedSla;
}
}
}
// Получаем operationId
String operationName = null;
if (colOperationName != -1) {
Cell nameCell = row.getCell(colOperationName);
if (nameCell != null) {
operationName = getCellValueAsString(nameCell);
}
}
if (operationName == null || operationName.isEmpty()) {
log.debug("Строка {} пропущена: нет operationName", rowNumber);
skippedCount++;
continue;
}
operationToData.put(operationName, new OldProfileData(fullService, intensity, sla));
rowCount++;
if (rowCount <= 10) {
log.info(" Загружена операция {}: '{}' -> интенсивность={}", rowCount, operationName, intensity);
}
}
log.info("Загружено {} операций из прошлого профиля (пропущено строк: {})", rowCount, skippedCount);
} catch (Exception e) {
log.error("Ошибка при чтении прошлого профиля: {}", e.getMessage(), e);
}
return operationToData;
}
/**
* Найти индекс колонки по названию
*/
private int findColumn(Row headerRow, String... possibleNames) {
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
Cell cell = headerRow.getCell(i);
if (cell != null) {
String cellValue = getCellValueAsString(cell);
if (cellValue != null) {
for (String name : possibleNames) {
if (cellValue.equalsIgnoreCase(name) || cellValue.contains(name)) {
return i;
}
}
}
}
}
return -1;
}
/**
* Получить значение ячейки как строку
*/
private String getCellValueAsString(Cell cell) {
if (cell == null) return null;
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
double numValue = cell.getNumericCellValue();
// Проверка на NaN и Infinity
if (Double.isNaN(numValue) || Double.isInfinite(numValue)) {
return null;
}
if (numValue == (long) numValue) {
return String.valueOf((long) numValue);
} else {
return String.valueOf(numValue);
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
try {
return cell.getStringCellValue();
} catch (IllegalStateException e) {
try {
double numericValue = cell.getNumericCellValue();
if (numericValue == (long) numericValue) {
return String.valueOf((long) numericValue);
} else {
return String.valueOf(numericValue);
}
} catch (Exception ex) {
return null;
}
}
default:
return null;
}
}
/**
* Получить значение ячейки как Integer
*/
private Integer getCellValueAsInteger(Cell cell) {
if (cell == null) return null;
try {
switch (cell.getCellType()) {
case NUMERIC:
return (int) cell.getNumericCellValue();
case STRING:
String strValue = cell.getStringCellValue().trim();
if (strValue.isEmpty()) return null;
strValue = strValue.replaceAll("[^0-9]", "");
if (strValue.isEmpty()) return null;
return Integer.parseInt(strValue);
case FORMULA:
try {
return (int) cell.getNumericCellValue();
} catch (Exception e) {
String formulaValue = cell.getStringCellValue();
if (formulaValue != null && !formulaValue.isEmpty()) {
formulaValue = formulaValue.replaceAll("[^0-9]", "");
if (!formulaValue.isEmpty()) {
return Integer.parseInt(formulaValue);
}
}
return null;
}
default:
return null;
}
} catch (Exception e) {
log.debug("Не удалось преобразовать ячейку в число: {}", e.getMessage());
return null;
}
}
}
package org.example.service;
import org.example.model.ProfileEntry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
public class ProfileAnalystParserService {
private static final String MOBILE_WRONG_PREFIX = "/ic/ufs/mobile/corp-retail/rest/";
private static final String MOBILE_CORRECT_PREFIX = "/ufs/mobile/corp-retail/rest/";
public List<ProfileEntry> parseProfileAnalyst(File profileFile, String projectType) {
List<ProfileEntry> entries = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new FileReader(profileFile, StandardCharsets.UTF_8))) {
String line;
boolean isFirstLine = true;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
line = line.trim();
if (line.isEmpty()) continue;
if (isFirstLine && (line.startsWith("URI") || line.toLowerCase().contains("uri"))) {
isFirstLine = false;
continue;
}
isFirstLine = false;
String[] parts = line.split(",", 2);
if (parts.length < 2) {
log.warn("Строка {} пропущена (неверный формат): {}", lineNumber, line);
continue;
}
String uriWithMethod = parts[0].trim();
int intensity;
try {
intensity = Integer.parseInt(parts[1].trim());
} catch (NumberFormatException e) {
log.warn("Строка {} пропущена (неверная интенсивность): {}", lineNumber, line);
continue;
}
// Очищаем URI от невидимых символов
uriWithMethod = cleanUri(uriWithMethod);
String uri, method;
if (uriWithMethod.contains(" ")) {
String[] splitParts = uriWithMethod.split(" ", 2); // ← переименовано
String first = splitParts[0].trim();
String second = splitParts[1].trim();
// Определяем, что где: если первая часть начинается с / - это URI
if (first.startsWith("/")) {
uri = first;
method = second.toUpperCase();
} else {
method = first.toUpperCase();
uri = second;
}
} else {
uri = uriWithMethod;
method = "GET";
}
// ========== НОРМАЛИЗАЦИЯ ПРЕФИКСА ДЛЯ МОБИЛКИ ==========
if ("MOBILE".equals(projectType)) {
// Приводим к нижнему регистру для проверки
String lowerUri = uri.toLowerCase();
// Убираем /ic/ufs/... → /ufs/...
if (lowerUri.startsWith("/ic/ufs/")) {
uri = "/ufs/" + uri.substring(5); // убираем "/ic/ufs/"
log.debug(" Исправлен префикс /ic/ufs/ → /ufs/: {}", uri);
}
// Убираем лишний /fs/ если появился (например, /ufs/fs/mobile/... → /ufs/mobile/...)
uri = uri.replaceFirst("/fs/", "/");
// Убираем множественные слеши
uri = uri.replaceAll("/+", "/");
}
// =====================================================
// Дополнительная очистка
uri = cleanUri(uri);
if (uri == null || uri.isEmpty()) {
log.warn("Строка {} пропущена (URI пуст после очистки): {}", lineNumber, line);
continue;
}
ProfileEntry entry = new ProfileEntry();
entry.setUri(uri);
entry.setMethod(method);
entry.setIntensity(intensity);
entries.add(entry);
log.debug("Добавлена операция: метод={}, uri={}, интенсивность={}", method, uri, intensity);
}
log.info("Загружено {} записей из профиля аналитика", entries.size());
} catch (Exception e) {
log.error("Ошибка при парсинге профиля аналитика: {}", e.getMessage());
}
return entries;
}
/**
* Агрессивная очистка URI от невидимых символов
*/
private String cleanUri(String uri) {
if (uri == null) return null;
// Убираем все управляющие символы
String cleaned = uri.replaceAll("[\\p{Cntrl}]", "");
// Убираем неразрывные пробелы и другие спецсимволы
cleaned = cleaned.replaceAll("[\\u00A0\\u2000-\\u200F\\u2028-\\u202F\\u205F\\u3000]", " ");
// Убираем символ � и другие непечатные
cleaned = cleaned.replaceAll("[^\\x20-\\x7E]", "");
// Заменяем множественные пробелы на один
cleaned = cleaned.replaceAll("\\s+", " ");
// Убираем пробелы в начале и конце
cleaned = cleaned.trim();
return cleaned;
}
}
package org.example.service;
import com.google.gson.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileReader;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Service
public class ScriptParserService {
private final Gson gson = new Gson();
public Map<String, List<String>> parseScriptAndBuildUrlToOpIdMap(File scriptFile, String urlPrefix) {
Map<String, List<String>> urlToOpIds = new HashMap<>();
try (FileReader reader = new FileReader(scriptFile)) {
JsonElement root = JsonParser.parseReader(reader);
AtomicInteger totalFound = new AtomicInteger(0);
AtomicInteger businessOpsCount = new AtomicInteger(0);
AtomicInteger authOps = new AtomicInteger(0);
traverseJson(root, urlToOpIds, urlPrefix, totalFound, businessOpsCount, authOps);
// Подсчитываем реальное количество уникальных операций (по operation_id)
Set<String> uniqueOperationIds = new HashSet<>();
for (List<String> opIds : urlToOpIds.values()) {
uniqueOperationIds.addAll(opIds);
}
log.info("========================================");
log.info("Статистика парсинга скрипта:");
log.info(" Всего операций с url и operation_id: {}", totalFound.get());
log.info(" Бизнес операции (префикс {}): {}", urlPrefix, businessOpsCount.get());
log.info(" Операции авторизации (остальные): {}", authOps.get());
log.info("========================================");
log.info(" Уникальных URI: {}", urlToOpIds.size());
log.info(" Уникальных операций (по operation_id): {}", uniqueOperationIds.size());
log.info("========================================");
} catch (Exception e) {
log.error("Ошибка при парсинге скрипта: {}", e.getMessage(), e);
}
return urlToOpIds;
}
private void traverseJson(JsonElement element,
Map<String, List<String>> urlToOpIds,
String urlPrefix,
AtomicInteger totalFound,
AtomicInteger businessOpsCount,
AtomicInteger authOps) {
if (element == null) return;
if (element.isJsonObject()) {
JsonObject obj = element.getAsJsonObject();
if (obj.has("url") && obj.has("operation_id") && obj.has("method")) {
totalFound.incrementAndGet();
String url = obj.get("url").getAsString();
String operationId = obj.get("operation_id").getAsString();
String cleanUrl = cleanUrl(url);
if (cleanUrl != null && cleanUrl.startsWith(urlPrefix)) {
businessOpsCount.incrementAndGet();
// Добавляем operation_id в список для этого URI
urlToOpIds.computeIfAbsent(cleanUrl, k -> new ArrayList<>()).add(operationId);
log.debug(" Бизнес: {} → {}", cleanUrl, operationId);
} else {
authOps.incrementAndGet();
log.debug(" Авторизация: {} → {}", cleanUrl, operationId);
}
}
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
traverseJson(entry.getValue(), urlToOpIds, urlPrefix,
totalFound, businessOpsCount, authOps);
}
} else if (element.isJsonArray()) {
JsonArray array = element.getAsJsonArray();
for (JsonElement item : array) {
traverseJson(item, urlToOpIds, urlPrefix,
totalFound, businessOpsCount, authOps);
}
}
}
private String cleanUrl(String url) {
if (url == null) return null;
String cleaned = url.trim();
int queryIndex = cleaned.indexOf('?');
if (queryIndex != -1) {
cleaned = cleaned.substring(0, queryIndex);
}
if (cleaned.endsWith("/")) {
cleaned = cleaned.substring(0, cleaned.length() - 1);
}
return cleaned;
}
}
package org.example.service;
import org.springframework.stereotype.Component;
@Component
public class UriNormalizer {
public String normalizeUri(String uri, String projectType) {
if (uri == null) return null;
// 1. Убираем невидимые символы
String cleaned = uri.replaceAll("[\\p{Cf}]", "");
// 2. Заменяем все виды пробелов на обычный
cleaned = cleaned.replaceAll("[\\s\\u00A0]+", " ");
// 3. Убираем пробелы в начале и конце
cleaned = cleaned.trim();
// 4. Приводим к нижнему регистру для единообразия
cleaned = cleaned.toLowerCase();
// 5. Убираем префиксы для разных проектов
if ("WEB".equals(projectType)) {
cleaned = cleaned.replaceFirst("^/ic/ufs/corp-retail/rest/?", "");
cleaned = cleaned.replaceFirst("^/ic/ufs/corp-retail/?", "");
cleaned = cleaned.replaceFirst("^/rest/?", "");
} else if ("MOBILE".equals(projectType)) {
cleaned = cleaned.replaceFirst("^/ic/ufs/mobile/corp-retail/rest/?", "");
cleaned = cleaned.replaceFirst("^/ufs/mobile/corp-retail/rest/?", "");
} else if ("PPRB".equals(projectType)) {
// Сначала убираем ведущий слеш
if (cleaned.startsWith("/")) {
cleaned = cleaned.substring(1);
}
// Потом убираем /api/ в начале
cleaned = cleaned.replaceFirst("^api/", "");
// Убираем все вхождения /api/ (для случаев /api//api/)
cleaned = cleaned.replaceAll("/api/", "/");
}
// 6. Убираем множественные слеши (один раз после всех замен)
cleaned = cleaned.replaceAll("/+", "/");
// 7. Убираем ведущий и trailing слеши
if (cleaned.startsWith("/")) cleaned = cleaned.substring(1);
if (cleaned.endsWith("/")) cleaned = cleaned.substring(0, cleaned.length() - 1);
// 8. Заменяем множественные подчёркивания на одно (если есть)
cleaned = cleaned.replaceAll("_+", "_");
// 9. Убираем любые оставшиеся пробелы
cleaned = cleaned.replaceAll(" ", "");
return cleaned;
}
public String normalizeService(String method, String uri, String projectType) {
String normalizedMethod = method.toUpperCase();
String normalizedUri = normalizeUri(uri, projectType);
return normalizedMethod + " " + normalizedUri;
}
public String normalizeString(String input) {
if (input == null) return null;
String cleaned = input.replaceAll("[\\p{Cf}]", "");
cleaned = cleaned.replaceAll("[\\s\\u00A0]+", " ");
cleaned = cleaned.trim();
cleaned = cleaned.toLowerCase();
return cleaned;
}
}
package org.example;
import org.example.service.GeneratorService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j
@SpringBootApplication
public class ProfileGeneratorApplication implements CommandLineRunner {
@Autowired
private GeneratorService generatorService;
public static void main(String[] args) {
// Проверяем, есть ли параметр для автоматического запуска
if (args.length > 0 && !args[0].equals("--server")) {
// Режим автоматического запуска без веб-сервера
SpringApplication app = new SpringApplication(ProfileGeneratorApplication.class);
app.setWebApplicationType(WebApplicationType.NONE);
app.run(args);
} else {
// Обычный запуск с веб-сервером
SpringApplication.run(ProfileGeneratorApplication.class, args);
}
}
@Override
public void run(String... args) throws Exception {
// Если передан параметр для автоматической генерации
if (args.length > 0) {
String projectNumber = args[0];
String projectType = convertProjectNumberToType(projectNumber);
if (projectType != null) {
log.info("Автоматический запуск для проекта: {}", projectType);
generatorService.generate(projectType);
//System.exit(0);
} else {
log.error("Неверный номер проекта: {}", projectNumber);
System.exit(1);
}
}
}
private String convertProjectNumberToType(String projectNumber) {
switch (projectNumber) {
case "1": return "WEB";
case "2": return "MOBILE";
case "3": return "PPRB";
default: return null;
}
}
}