Загрузка данных
package org.example.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.example.model.CsvRow;
import org.example.model.OldProfileData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Slf4j
@Service
public class ExcelReportService {
@Autowired
private UriNormalizer uriNormalizer;
private static final String DOC_FOLDER = "/home/retb_test/PPRB_OTP/Tools/GeneratorProfile" + "/DocumentForTheScript";
private static final String TEMPLATE_FILE = "Анкета ЕФС web и mobile.xlsx";
private static final String STATIC_OPERATION_ID = "UC00_StaticJS_1_WEB";
private static final String GET_CONTACTS_OPERATION_ID = "UC29_GET_contacts_WEB";
// Индексы колонок для WEB (8 колонок)
private static final int COL_SCRIPT = 0;
private static final int COL_SERVICE_WEB = 1;
private static final int COL_TPH_WEB = 2;
private static final int COL_SLA_WEB = 3;
private static final int COL_LOGIN_WEB = 4;
private static final int COL_THREADS_WEB = 5;
private static final int COL_PACING_WEB = 6;
private static final int COL_ITERATIONS_WEB = 7;
// Индексы колонок для MOBILE (8 колонок)
private static final int COL_SCRIPT_MOB = 0;
private static final int COL_SERVICE_MOB = 1;
private static final int COL_TPH_MOB = 2;
private static final int COL_SLA_MOB = 3;
private static final int COL_LOGIN_MOB = 4;
private static final int COL_THREADS_MOB = 5;
private static final int COL_PACING_MOB = 6;
private static final int COL_ITERATIONS_MOB = 7;
public void generateExcelReport(List<CsvRow> newRows, String projectType,
Map<String, Integer> oldIntensityByService,
Set<String> oldServices,
Set<String> extraInScript,
Map<String, OldProfileData> lastProfileData) {
try {
File templateFile = new File(DOC_FOLDER, TEMPLATE_FILE);
String pagePrefix = "WEB".equals(projectType) ? "WEB" : "MOB";
Workbook workbook;
if (templateFile.exists()) {
workbook = new XSSFWorkbook(new FileInputStream(templateFile));
} else {
workbook = new XSSFWorkbook();
}
// Удаляем пустые страницы
for (int i = workbook.getNumberOfSheets() - 1; i >= 0; i--) {
String sheetName = workbook.getSheetName(i);
if (sheetName.startsWith(pagePrefix) && workbook.getSheetAt(i).getLastRowNum() == 0) {
workbook.removeSheetAt(i);
}
}
Sheet lastSheet = findLastSheetByPrefix(workbook, pagePrefix);
int nextReleaseNumber;
String newSheetName;
if (lastSheet == null) {
nextReleaseNumber = 5038;
newSheetName = pagePrefix + " 5.038";
} else {
int lastReleaseNumber = extractReleaseNumber(lastSheet.getSheetName());
nextReleaseNumber = lastReleaseNumber + 1;
newSheetName = pagePrefix + " " + formatReleaseNumber(nextReleaseNumber);
}
Sheet newSheet = workbook.createSheet(newSheetName);
if (lastSheet != null) {
copySheetStructure(lastSheet, newSheet);
removeDelayAndFixHeaders(newSheet, projectType);
removeHeaderRowIfExists(newSheet);
} else {
createHeaderRow(newSheet, projectType);
}
fillSheetWithData(newSheet, newRows, oldIntensityByService, oldServices, projectType, extraInScript, lastProfileData);
removeHeaderRowIfExists(newSheet);
try (FileOutputStream fos = new FileOutputStream(templateFile)) {
workbook.write(fos);
}
workbook.close();
log.info(" Excel отчёт обновлён: добавлена страница {}", newSheetName);
} catch (IOException e) {
log.error("Ошибка при создании Excel отчёта: {}", e.getMessage());
}
}
private void removeDelayAndFixHeaders(Sheet sheet, String projectType) {
Row oldHeaderRow = sheet.getRow(0);
if (oldHeaderRow != null) {
sheet.removeRow(oldHeaderRow);
}
Row newHeaderRow = sheet.createRow(0);
String[] headers;
if ("MOBILE".equals(projectType)) {
headers = new String[]{"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Логинов в час",
"Количество потоков", "Pacing", "Количество итераций"};
} else {
headers = new String[]{"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Логинов в час",
"Количество потоков", "Pacing", "Количество итераций"};
}
for (int i = 0; i < headers.length; i++) {
Cell cell = newHeaderRow.createCell(i);
cell.setCellValue(headers[i]);
sheet.setColumnWidth(i, (i == 7 || i == 8) ? 8000 : 5000);
}
Font headerFont = sheet.getWorkbook().createFont();
headerFont.setBold(true);
CellStyle headerStyle = sheet.getWorkbook().createCellStyle();
headerStyle.setFont(headerFont);
setBorders(headerStyle);
for (int i = 0; i < headers.length; i++) {
newHeaderRow.getCell(i).setCellStyle(headerStyle);
}
int maxCol = headers.length;
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row != null) {
for (int c = row.getLastCellNum() - 1; c >= maxCol; c--) {
Cell cell = row.getCell(c);
if (cell != null) {
row.removeCell(cell);
}
}
}
}
}
private void removeHeaderRowIfExists(Sheet sheet) {
for (int r = sheet.getLastRowNum(); r >= 0; r--) {
Row row = sheet.getRow(r);
if (row == null) continue;
for (int c = 0; c < row.getLastCellNum(); c++) {
Cell cell = row.getCell(c);
if (cell != null) {
String cellValue = null;
try {
cellValue = cell.getStringCellValue();
} catch (IllegalStateException e) {
continue;
}
if (cellValue != null && cellValue.contains("Наименование операции")) {
sheet.shiftRows(r + 1, sheet.getLastRowNum(), -1);
log.info(" Удалена строка с содержимым: {}", cellValue);
break;
}
}
}
}
}
private Sheet findLastSheetByPrefix(Workbook workbook, String prefix) {
Sheet lastSheet = null;
int maxRelease = -1;
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
String sheetName = workbook.getSheetName(i);
if (sheetName.startsWith(prefix)) {
int releaseNum = extractReleaseNumber(sheetName);
if (releaseNum > maxRelease) {
maxRelease = releaseNum;
lastSheet = workbook.getSheetAt(i);
}
}
}
return lastSheet;
}
private int extractReleaseNumber(String sheetName) {
Pattern pattern = Pattern.compile("(\\d+)\\.(\\d+)");
Matcher matcher = pattern.matcher(sheetName);
if (matcher.find()) {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
return major * 1000 + minor;
}
return 0;
}
private String formatReleaseNumber(int releaseNumber) {
int major = releaseNumber / 1000;
int minor = releaseNumber % 1000;
return major + "." + String.format("%03d", minor);
}
private void createHeaderRow(Sheet sheet, String projectType) {
Row headerRow = sheet.createRow(0);
String[] headers;
if ("MOBILE".equals(projectType)) {
headers = new String[]{"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Логинов в час",
"Количество потоков", "Pacing", "Количество итераций"};
} else {
headers = new String[]{"Скрипт", "Вызываемый сервис", "TPH", "SLA", "Логинов в час",
"Количество потоков", "Pacing", "Количество итераций"};
}
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
sheet.setColumnWidth(i, (i == 7 || i == 8) ? 8000 : 5000);
}
Font headerFont = sheet.getWorkbook().createFont();
headerFont.setBold(true);
CellStyle headerStyle = sheet.getWorkbook().createCellStyle();
headerStyle.setFont(headerFont);
setBorders(headerStyle);
for (int i = 0; i < headers.length; i++) {
headerRow.getCell(i).setCellStyle(headerStyle);
}
}
private void copySheetStructure(Sheet source, Sheet target) {
if (source.getRow(0) != null) {
for (int i = 0; i < source.getRow(0).getLastCellNum(); i++) {
target.setColumnWidth(i, source.getColumnWidth(i));
}
}
for (int r = 0; r <= source.getLastRowNum(); r++) {
Row sourceRow = source.getRow(r);
if (sourceRow != null) {
Row targetRow = target.createRow(r);
for (int c = 0; c < sourceRow.getLastCellNum(); c++) {
Cell sourceCell = sourceRow.getCell(c);
if (sourceCell != null) {
Cell targetCell = targetRow.createCell(c);
switch (sourceCell.getCellType()) {
case STRING:
targetCell.setCellValue(sourceCell.getStringCellValue());
break;
case NUMERIC:
targetCell.setCellValue(sourceCell.getNumericCellValue());
break;
case BOOLEAN:
targetCell.setCellValue(sourceCell.getBooleanCellValue());
break;
case FORMULA:
targetCell.setCellFormula(sourceCell.getCellFormula());
break;
default:
targetCell.setCellValue("");
}
CellStyle newStyle = target.getWorkbook().createCellStyle();
newStyle.cloneStyleFrom(sourceCell.getCellStyle());
targetCell.setCellStyle(newStyle);
}
}
}
}
}
private void fillSheetWithData(Sheet sheet, List<CsvRow> newRows,
Map<String, Integer> oldIntensityByService,
Set<String> oldServices,
String projectType,
Set<String> extraInScript,
Map<String, OldProfileData> lastProfileData) {
// ========== СИНХРОНИЗАЦИЯ СТАТИКИ С UC29 (ДЛЯ WEB) ==========
if ("WEB".equals(projectType)) {
CsvRow contactsRow = null;
CsvRow staticRow = null;
int contactsIndex = -1;
int staticIndex = -1;
for (int i = 0; i < newRows.size(); i++) {
CsvRow row = newRows.get(i);
if (GET_CONTACTS_OPERATION_ID.equals(row.getName())) {
contactsRow = row;
contactsIndex = i;
} else if (STATIC_OPERATION_ID.equals(row.getName())) {
staticRow = row;
staticIndex = i;
}
}
if (contactsRow != null && staticRow != null) {
int contactsIntensity = contactsRow.getIntensity();
if (contactsIntensity != staticRow.getIntensity()) {
CsvRow updatedStaticRow = new CsvRow(
staticRow.getName(),
staticRow.getUri(),
staticRow.getMethod(),
contactsIntensity,
staticRow.getSla(),
staticRow.getTransactionName(),
staticRow.getRequirement(),
staticRow.getComment()
);
newRows.set(staticIndex, updatedStaticRow);
log.info(" Excel: синхронизирована интенсивность {} -> {} (как у {})",
STATIC_OPERATION_ID, contactsIntensity, GET_CONTACTS_OPERATION_ID);
}
}
}
// ========== ДЛЯ MOBILE: полная перезапись страницы ==========
if ("MOBILE".equals(projectType)) {
for (int r = sheet.getLastRowNum(); r >= 1; r--) {
Row row = sheet.getRow(r);
if (row != null) {
sheet.removeRow(row);
}
}
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) {
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();
if (csvRow.getIntensity() == 0) {
log.info("MOBILE: найдена операция с 0 - метод={}, uri={}, redKey={}",
csvRow.getMethod(), csvRow.getUri(), redKey);
log.info("MOBILE: extraInScript.contains(redKey) = {}",
extraInScript != null && extraInScript.contains(redKey));
log.info("MOBILE: oldServices.contains(serviceKey) = {}",
oldServices != null && oldServices.contains(
uriNormalizer.normalizeService(csvRow.getMethod(), csvRow.getUri(), projectType)));
}
boolean shouldBeRed = csvRow.getIntensity() == 0
&& extraInScript != null
&& extraInScript.contains(redKey);
if (csvRow.getIntensity() == 0) {
log.debug("MOBILE: shouldBeRed = {}", shouldBeRed);
}
if (shouldBeRed) {
log.info(" MOBILE: красим красным {} {} (интенсивность=0)",
csvRow.getMethod(), csvRow.getUri());
}
String serviceKey = uriNormalizer.normalizeService(csvRow.getMethod(), csvRow.getUri(), projectType);
boolean isNew = oldServices != null && !oldServices.contains(serviceKey)
&& (lastProfileData == null || !lastProfileData.containsKey(csvRow.getName()));
Integer oldTph = oldIntensityByService != null ? oldIntensityByService.get(serviceKey) : null;
boolean isIntensityChanged = oldTph != null && !oldTph.equals(csvRow.getIntensity()) && csvRow.getIntensity() > 0;
int currentTph = csvRow.getIntensity();
int iterations = shouldBeRed ? 1 : calculateIterations(currentTph);
int threads = shouldBeRed ? 1 : calculateThreads(currentTph);
CellStyle rowStyle;
if (shouldBeRed) {
rowStyle = badStyle;
} else if (isNew) {
rowStyle = goodStyle;
} else {
rowStyle = defaultStyle;
}
Cell cellScript = dataRow.createCell(COL_SCRIPT_MOB);
cellScript.setCellValue(csvRow.getName());
cellScript.setCellStyle(rowStyle);
Cell cellService = dataRow.createCell(COL_SERVICE_MOB);
cellService.setCellValue(csvRow.getMethod() + " " + csvRow.getUri());
cellService.setCellStyle(rowStyle);
Cell cellTph = dataRow.createCell(COL_TPH_MOB);
cellTph.setCellValue(currentTph);
cellTph.setCellStyle(rowStyle);
Cell cellSla = dataRow.createCell(COL_SLA_MOB);
cellSla.setCellValue(csvRow.getSla());
cellSla.setCellStyle(rowStyle);
Cell cellIterations = dataRow.createCell(COL_ITERATIONS_MOB);
cellIterations.setCellValue(iterations);
cellIterations.setCellStyle(rowStyle);
Cell cellThreads = dataRow.createCell(COL_THREADS_MOB);
cellThreads.setCellValue(threads);
cellThreads.setCellStyle(rowStyle);
Cell cellLogin = dataRow.createCell(COL_LOGIN_MOB);
if (shouldBeRed) {
cellLogin.setCellValue(0);
} else {
String tphColLetter = getColumnLetter(COL_TPH_MOB);
String iterColLetter = getColumnLetter(COL_ITERATIONS_MOB);
String formula = String.format("ROUNDUP(%s%d/%s%d,0)",
tphColLetter, currentRow + 1, iterColLetter, currentRow + 1);
cellLogin.setCellFormula(formula);
}
cellLogin.setCellStyle(rowStyle);
Cell cellPacing = dataRow.createCell(COL_PACING_MOB);
if (shouldBeRed) {
cellPacing.setCellValue(0);
} else {
int pacing = 0;
if (iterations > 0 && threads > 0) {
pacing = (int) Math.round(3600.0 / (((double) currentTph / iterations) / threads));
}
if (pacing > 300 && iterations > 1) {
int[] validIterations = {1, 5, 10, 20, 40, 60};
int newIterations = iterations;
int newPacing = pacing;
for (int i = validIterations.length - 1; i >= 0; i--) {
int candidateIterations = validIterations[i];
if (candidateIterations < iterations) {
int candidatePacing = (int) Math.round(3600.0 / (((double) currentTph / candidateIterations) / threads));
if (candidatePacing <= 300) {
newIterations = candidateIterations;
newPacing = candidatePacing;
break;
}
}
}
Cell iterCell = dataRow.getCell(COL_ITERATIONS_MOB);
if (iterCell != null && newIterations != iterations) {
iterCell.setCellValue(newIterations);
log.debug(" MOBILE: скорректированы итерации: {} -> {} (пейсинг стал {})",
iterations, newIterations, newPacing);
}
}
String loginColLetter = getColumnLetter(COL_LOGIN_MOB);
String threadsColLetter = getColumnLetter(COL_THREADS_MOB);
String formula = String.format("ROUND(3600/(%s%d/%s%d),0)",
loginColLetter, currentRow + 1, threadsColLetter, currentRow + 1);
cellPacing.setCellFormula(formula);
}
cellPacing.setCellStyle(rowStyle);
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);
}
}
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);
}
}
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;
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);
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;
}
}
}
}
CsvRow staticRow = null;
Iterator<CsvRow> iterator = newRows.iterator();
while (iterator.hasNext()) {
CsvRow row = iterator.next();
if ("UC00_StaticJS_1_WEB".equals(row.getName())) {
staticRow = row;
iterator.remove();
break;
}
}
if (staticRow != null) {
Integer staticRowNum = null;
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row != null && colScript >= 0) {
Cell scriptCell = row.getCell(colScript);
if (scriptCell != null) {
String scriptValue = null;
try {
scriptValue = scriptCell.getStringCellValue();
} catch (IllegalStateException e) {
continue;
}
if ("UC00_StaticJS_1_WEB".equals(scriptValue)) {
staticRowNum = r;
break;
}
}
}
}
Row staticRowExcel;
int staticRowIndex;
if (staticRowNum == null) {
int lastRowNum = sheet.getLastRowNum();
if (lastRowNum >= 1) {
sheet.shiftRows(1, lastRowNum, 1);
}
staticRowExcel = sheet.createRow(1);
staticRowIndex = 1;
staticRowExcel.createCell(colScript).setCellValue(staticRow.getName());
staticRowExcel.createCell(colService).setCellValue(staticRow.getMethod() + " " + staticRow.getUri());
log.info(" Добавлена строка со статическим запросом UC00_StaticJS_1_WEB");
} else {
staticRowExcel = sheet.getRow(staticRowNum);
staticRowIndex = staticRowNum;
log.info(" Обновлена статическая операция UC00_StaticJS_1_WEB");
}
String serviceKey = uriNormalizer.normalizeService(staticRow.getMethod(), staticRow.getUri(), projectType);
boolean isNew = oldServices != null && !oldServices.contains(serviceKey)
&& (lastProfileData == null || !lastProfileData.containsKey(staticRow.getName()));
Integer oldTph = oldIntensityByService != null ? oldIntensityByService.get(serviceKey) : null;
boolean isChanged = oldTph != null && !oldTph.equals(staticRow.getIntensity());
CellStyle staticStyle = (isNew || isChanged) ? goodStyle : defaultStyle;
if (isChanged) {
log.info(" Статика {} изменила интенсивность: {} -> {}",
STATIC_OPERATION_ID, oldTph, staticRow.getIntensity());
}
Cell tphCell = staticRowExcel.getCell(colTph);
if (tphCell == null) tphCell = staticRowExcel.createCell(colTph);
tphCell.setCellValue(staticRow.getIntensity());
tphCell.setCellStyle(staticStyle);
Cell slaCell = staticRowExcel.getCell(colSla);
if (slaCell == null) slaCell = staticRowExcel.createCell(colSla);
slaCell.setCellValue(staticRow.getSla());
slaCell.setCellStyle(defaultStyle);
int iterations = calculateIterations(staticRow.getIntensity());
Cell iterCell = staticRowExcel.getCell(colIterations);
if (iterCell == null) iterCell = staticRowExcel.createCell(colIterations);
iterCell.setCellValue(iterations);
iterCell.setCellStyle(defaultStyle);
int threads = calculateThreads(staticRow.getIntensity());
Cell threadsCell = staticRowExcel.getCell(colThreads);
if (threadsCell == null) threadsCell = staticRowExcel.createCell(colThreads);
threadsCell.setCellValue(threads);
threadsCell.setCellStyle(defaultStyle);
Cell loginCell = staticRowExcel.getCell(colLogin);
if (loginCell == null) loginCell = staticRowExcel.createCell(colLogin);
loginCell.setCellValue(0);
loginCell.setCellStyle(defaultStyle);
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);
}
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;
}
}
}
}
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);
}
}
}
}
Set<String> processedNames = new HashSet<>();
for (CsvRow row : newRows) {
String serviceKey = uriNormalizer.normalizeService(row.getMethod(), row.getUri(), projectType);
String redKey = row.getMethod() + "|" + row.getUri();
boolean shouldBeRed = row.getIntensity() == 0
&& extraInScript != null
&& extraInScript.contains(redKey);
if (shouldBeRed) {
log.info(" WEB: красим красным {} {} (интенсивность=0)",
row.getMethod(), row.getUri());
}
if (processedNames.contains(row.getName())) {
log.warn("Пропущен дубликат операции в newRows: {}", row.getName());
continue;
}
boolean isNew = oldServices != null && !oldServices.contains(serviceKey)
&& (lastProfileData == null || !lastProfileData.containsKey(row.getName()));
Integer oldTph = oldIntensityByService != null ? oldIntensityByService.get(serviceKey) : null;
boolean isIntensityChanged = oldTph != null && !oldTph.equals(row.getIntensity()) && row.getIntensity() > 0;
Integer existingRowNum = existingRowsMap.get(serviceKey);
Row dataRow;
int currentRowNum;
if (existingRowNum != null) {
dataRow = sheet.getRow(existingRowNum);
currentRowNum = existingRowNum;
if (existingRowNum > maxRowNum) maxRowNum = existingRowNum;
} else {
maxRowNum++;
currentRowNum = maxRowNum;
dataRow = sheet.createRow(maxRowNum);
if (colScript >= 0) {
dataRow.createCell(colScript).setCellValue(row.getName());
}
dataRow.createCell(colService).setCellValue(row.getMethod() + " " + row.getUri());
}
processedNames.add(row.getName());
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;
}
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;
}
fillRowDataWeb(dataRow, row, currentRowNum, colScript, colService, colTph, colSla,
colIterations, colThreads, colLogin, colPacing, defaultStyle, false);
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);
Cell tphCell = dataRow.getCell(colTph);
if (tphCell == null) tphCell = dataRow.createCell(colTph);
tphCell.setCellValue(currentTph);
tphCell.setCellStyle(style);
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 {
String loginColLetter = getColumnLetter(colLogin);
String threadsColLetter = getColumnLetter(colThreads);
String formula = String.format("ROUND(3600/(%s%d/%s%d),0)",
loginColLetter, rowNum + 1, threadsColLetter, rowNum + 1);
pacingCell.setCellFormula(formula);
}
}
pacingCell.setCellStyle(style);
}
private void setBorders(CellStyle style) {
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
}
private int calculateThreads(int tph) {
if (tph <= 100) return 3;
if (tph <= 200) return 6;
if (tph <= 500) return 8;
if (tph <= 1000) return 10;
if (tph <= 10000) return 20;
if (tph <= 15000) return 24;
if (tph <= 20000) return 28;
if (tph <= 25000) return 32;
if (tph <= 30000) return 36;
if (tph <= 40000) return 40;
if (tph <= 50000) return 44;
return 60;
}
private int calculateIterations(int tph) {
if (tph <= 900) return 1;
if (tph <= 1000) return 10;
if (tph <= 20000) return 20;
if (tph <= 40000) return 40;
return 60;
}
private String getColumnLetter(int colIndex) {
StringBuilder letter = new StringBuilder();
while (colIndex >= 0) {
letter.insert(0, (char) ('A' + (colIndex % 26)));
colIndex = colIndex / 26 - 1;
}
return letter.toString();
}
private String cleanUriFromParams(String uri) {
if (uri == null) return null;
String cleaned = uri.replaceAll("[\\p{Cf}]", "");
int queryIndex = cleaned.indexOf('?');
if (queryIndex != -1) {
return cleaned.substring(0, queryIndex);
}
return cleaned;
}
public void generatePprbExcelReport(List<CsvRow> newRows, String projectType,
Map<String, Integer> oldIntensityByService,
Set<String> oldServices,
Set<String> extraInScript,
Map<String, OldProfileData> lastProfileData) {
List<CsvRow> filteredRows = new ArrayList<>();
for (CsvRow row : newRows) {
String normalizedUri = uriNormalizer.normalizeUri(row.getUri(), "PPRB");
String redKey = row.getMethod() + "|" + normalizedUri;
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, lastProfileData);
removeHeaderRowIfExists(newSheet);
try (FileOutputStream fos = new FileOutputStream(templateFile)) {
workbook.write(fos);
}
workbook.close();
log.info(" ППРБ Excel отчёт обновлён: добавлена страница {}", newSheetName);
} catch (IOException e) {
log.error("Ошибка при создании ППРБ Excel отчёта: {}", e.getMessage());
}
}
private void 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,
Map<String, OldProfileData> lastProfileData) {
for (int r = sheet.getLastRowNum(); r >= 1; r--) {
Row row = sheet.getRow(r);
if (row != null) {
sheet.removeRow(row);
}
}
Row row2 = sheet.getRow(2);
if (row2 != null) {
boolean isEmpty = true;
for (int c = 0; c < row2.getLastCellNum(); c++) {
Cell cell = row2.getCell(c);
if (cell != null && cell.getCellType() != CellType.BLANK) {
if (cell.getCellType() == CellType.STRING && !cell.getStringCellValue().isEmpty()) {
isEmpty = false;
break;
} else if (cell.getCellType() == CellType.NUMERIC) {
isEmpty = false;
break;
}
}
}
if (isEmpty && sheet.getLastRowNum() >= 3) {
sheet.shiftRows(3, sheet.getLastRowNum(), -1);
}
}
log.info(" ППРБ страница очищена, будет создано {} новых строк", newRows.size());
Workbook workbook = sheet.getWorkbook();
CellStyle goodStyle = workbook.createCellStyle();
goodStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
goodStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorders(goodStyle);
CellStyle badStyle = workbook.createCellStyle();
badStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
badStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorders(badStyle);
CellStyle defaultStyle = workbook.createCellStyle();
setBorders(defaultStyle);
int colScript = 0;
int colService = 1;
int colTph = 2;
int colSla = 3;
int colThreads = 4;
int colPacing = 5;
int currentRow = 1;
for (CsvRow row : newRows) {
String normalizedUri = uriNormalizer.normalizeUri(row.getUri(), "PPRB");
String serviceKey = uriNormalizer.normalizeService(row.getMethod(), row.getUri(), "PPRB");
String redKey = row.getMethod() + "|" + normalizedUri;
boolean shouldBeRed = row.getIntensity() == 0
&& extraInScript != null
&& extraInScript.contains(redKey);
boolean isNew = (oldServices == null || !oldServices.contains(serviceKey))
&& (lastProfileData == null || !lastProfileData.containsKey(row.getName()));
Integer oldTph = oldIntensityByService != null ? oldIntensityByService.get(serviceKey) : null;
boolean isIntensityChanged = oldTph != null && !oldTph.equals(row.getIntensity()) && row.getIntensity() > 0;
Row dataRow = sheet.createRow(currentRow);
CellStyle rowStyle;
if (shouldBeRed) {
rowStyle = badStyle;
log.info(" ППРБ: красим красным {} {} (интенсивность=0)",
row.getMethod(), row.getUri());
} else if (isNew) {
rowStyle = goodStyle;
log.info(" ППРБ: новая операция {} {}", row.getMethod(), row.getUri());
} else {
rowStyle = defaultStyle;
}
int currentTph = row.getIntensity();
int threads = calculateThreads(currentTph);
Cell cellScript = dataRow.createCell(colScript);
cellScript.setCellValue(row.getName());
cellScript.setCellStyle(rowStyle);
Cell cellService = dataRow.createCell(colService);
cellService.setCellValue(row.getMethod() + " " + row.getUri());
cellService.setCellStyle(rowStyle);
Cell cellTph = dataRow.createCell(colTph);
cellTph.setCellValue(currentTph);
cellTph.setCellStyle(rowStyle);
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);
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);
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());
}
}