debug: добавлена детальная отладка импорта CSV

- Добавлено логирование парсинга CSV на фронтенде
- Добавлено логирование отправляемых данных
- Добавлено детальное логирование в PHP бэкенде (каждая строка импорта)
- Улучшена обработка ошибок с выводом подробной информации
- Добавлен вывод SQL запросов при ошибках
This commit is contained in:
2026-01-21 03:40:17 +03:00
parent fff4e80ffd
commit 66804814f1
3 changed files with 118 additions and 17 deletions

View File

@@ -580,12 +580,17 @@ document.getElementById('csvFileInput').addEventListener('change', async (e) =>
const file = e.target.files[0];
if (!file) return;
console.log('=== НАЧАЛО ИМПОРТА CSV ===');
console.log('Файл:', file.name, 'Размер:', file.size, 'байт');
try {
const text = await file.text();
console.log('Содержимое файла:', text.substring(0, 200)); // первые 200 символов для отладки
console.log('Содержимое файла (первые 500 символов):\n', text.substring(0, 500));
console.log('Полное содержимое файла:\n', text);
const rows = parseCSV(text);
console.log('Распарсенные строки:', rows);
console.log('Всего строк после парсинга:', rows.length);
console.log('Все распарсенные строки:', rows);
if (rows.length === 0) {
alert('CSV файл пуст');
@@ -593,9 +598,10 @@ document.getElementById('csvFileInput').addEventListener('change', async (e) =>
}
const headers = rows[0];
console.log('Заголовки:', headers);
console.log('Заголовки CSV:', headers);
const dataRows = rows.slice(1);
console.log('Строк данных (без заголовка):', dataRows.length);
if (dataRows.length === 0) {
alert('Нет данных для импорта (только заголовки)');
@@ -603,15 +609,25 @@ document.getElementById('csvFileInput').addEventListener('change', async (e) =>
}
// Преобразуем в массив объектов
const records = dataRows.map(row => {
const records = dataRows.map((row, idx) => {
const obj = {};
headers.forEach((header, i) => {
obj[header] = row[i] || null;
const value = row[i] || null;
obj[header] = value;
console.log(` Строка ${idx + 1}, столбец "${header}": "${value}"`);
});
return obj;
});
console.log('Импортируем записи:', records);
console.log('Финальные записи для отправки на сервер:', JSON.stringify(records, null, 2));
console.log('Количество записей:', records.length);
// Отправляем на сервер
console.log('Отправка запроса на сервер:', {
schema: currentSchema,
table: currentTable,
rowCount: records.length
});
const result = await api('/api/table/import-csv', 'POST', {
schema: currentSchema,
@@ -619,17 +635,29 @@ document.getElementById('csvFileInput').addEventListener('change', async (e) =>
rows: records
});
alert(`✓ Импортировано строк: ${result.inserted}\nОшибок: ${result.errors}`);
console.log('Ответ от сервера:', result);
if (result.errorMessages && result.errorMessages.length > 0) {
console.error('Ошибки импорта:', result.errorMessages);
alert(`Импортировано строк: ${result.inserted}\nОшибок: ${result.errors}\n\nОшибки:\n${result.errorMessages.join('\n')}`);
} else {
alert(`✓ Импортировано строк: ${result.inserted}\nОшибок: ${result.errors}`);
}
await table.replaceData();
// Очищаем input
e.target.value = '';
} catch (err) {
console.error('Ошибка импорта:', err);
console.error('=== КРИТИЧЕСКАЯ ОШИБКА ИМПОРТА ===');
console.error('Тип ошибки:', err.name);
console.error('Сообщение:', err.message);
console.error('Stack:', err.stack);
alert('Ошибка импорта: ' + err.message);
}
});
// ========== ЭКСПОРТ CSV ==========
document.getElementById('btnExportCSV').addEventListener('click', async () => {
if (!currentSchema || !currentTable || !table) {

View File

@@ -172,22 +172,63 @@ $app->post('/api/table/delete', function (Request $request, Response $response)
// API: импорт CSV (массовая вставка)
$app->post('/api/table/import-csv', function (Request $request, Response $response) use ($container) {
$payload = json_decode((string)$request->getBody(), true);
error_log("\n========== ЗАПРОС НА ИМПОРТ CSV ==========");
$body = (string)$request->getBody();
error_log("Тело запроса (первые 1000 символов): " . substr($body, 0, 1000));
$payload = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("ОШИБКА JSON: " . json_last_error_msg());
$response->getBody()->write(json_encode(['error' => 'Invalid JSON: ' . json_last_error_msg()]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
error_log("Декодированный payload: " . json_encode($payload, JSON_PRETTY_PRINT));
$pdo = $container->get('db');
$meta = new \App\MetaService($pdo);
$ds = new \App\DataService($pdo);
$schema = $payload['schema'];
$table = $payload['table'];
$schema = $payload['schema'] ?? '';
$table = $payload['table'] ?? '';
$rows = $payload['rows'] ?? [];
$metaArr = $meta->getTableMeta($schema, $table);
$result = $ds->insertMultipleRows($schema, $table, $rows, $metaArr['columns']);
error_log("Schema: $schema");
error_log("Table: $table");
error_log("Количество строк: " . count($rows));
$response->getBody()->write(json_encode($result));
return $response->withHeader('Content-Type', 'application/json');
if (empty($schema) || empty($table)) {
error_log("ОШИБКА: пустая schema или table");
$response->getBody()->write(json_encode(['error' => 'Schema and table are required']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
try {
$metaArr = $meta->getTableMeta($schema, $table);
error_log("Метаданные таблицы получены, столбцов: " . count($metaArr['columns']));
$result = $ds->insertMultipleRows($schema, $table, $rows, $metaArr['columns']);
error_log("Результат импорта: " . json_encode($result));
$response->getBody()->write(json_encode($result));
return $response->withHeader('Content-Type', 'application/json');
} catch (\Exception $e) {
error_log("КРИТИЧЕСКАЯ ОШИБКА: " . $e->getMessage());
error_log("Stack trace: " . $e->getTraceAsString());
$response->getBody()->write(json_encode([
'error' => $e->getMessage(),
'inserted' => 0,
'errors' => 1
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(500);
}
});
// API: экспорт CSV
$app->post('/api/table/export-csv', function (Request $request, Response $response) use ($container) {
$payload = json_decode((string)$request->getBody(), true);

View File

@@ -241,6 +241,11 @@ class DataService
public function insertMultipleRows(string $schema, string $table, array $rows, array $columns): array
{
error_log("=== НАЧАЛО ИМПОРТА CSV ===");
error_log("Schema: $schema, Table: $table");
error_log("Количество строк для импорта: " . count($rows));
error_log("Структура первой строки: " . json_encode($rows[0] ?? []));
if (empty($rows)) {
return ['inserted' => 0, 'errors' => 0, 'message' => 'No rows provided'];
}
@@ -255,6 +260,8 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a
$name = $c['COLUMN_NAME'];
$extra = $c['EXTRA'] ?? '';
error_log("Столбец: $name, Extra: $extra, Nullable: " . ($c['IS_NULLABLE'] ? 'YES' : 'NO') . ", Default: " . ($c['COLUMN_DEFAULT'] ?? 'NULL'));
// Пропускаем только auto_increment поля
if (!str_contains($extra, 'auto_increment')) {
$validColumns[$name] = [
@@ -262,13 +269,20 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a
'has_default' => !empty($c['COLUMN_DEFAULT']) || $c['COLUMN_DEFAULT'] === '0',
'default' => $c['COLUMN_DEFAULT'] ?? null
];
} else {
error_log("Пропускаем auto_increment столбец: $name");
}
}
error_log("Валидные столбцы для импорта: " . json_encode(array_keys($validColumns)));
$this->pdo->beginTransaction();
try {
foreach ($rows as $index => $row) {
error_log("\n--- Обработка строки " . ($index + 1) . " ---");
error_log("Данные строки: " . json_encode($row));
try {
$insertCols = [];
$placeholders = [];
@@ -281,22 +295,28 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a
// Если столбец есть в CSV строке
if (array_key_exists($name, $row)) {
$value = $row[$name];
error_log(" Столбец '$name' найден в CSV: '$value'");
// ✅ Обрабатываем "NULL" как NULL
if (in_array($value, ['NULL', 'null', ''], true)) {
error_log(" Преобразуем '$value' в NULL");
$value = null;
}
} else {
error_log(" Столбец '$name' НЕ найден в CSV");
// ✅ Столбца нет в CSV - устанавливаем NULL или пропускаем
// Если поле имеет значение по умолчанию - пропускаем (БД сама подставит)
if ($info['has_default']) {
error_log(" Пропускаем (есть default): " . $info['default']);
continue;
}
// Если поле nullable - ставим NULL
if ($info['nullable']) {
error_log(" Устанавливаем NULL (nullable)");
$value = null;
} else {
// Поле обязательное и нет дефолта - ошибка
error_log(" ОШИБКА: обязательное поле без значения");
throw new \PDOException("Missing required field: $name");
}
}
@@ -304,9 +324,11 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a
$insertCols[] = "`$name`";
$placeholders[] = ":$name";
$params[":$name"] = $value;
error_log(" Добавлено в INSERT: '$name' = " . ($value === null ? 'NULL' : "'$value'"));
}
if (empty($insertCols)) {
error_log("Нет столбцов для вставки, пропускаем строку");
continue;
}
@@ -317,18 +339,28 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a
implode(',', $placeholders)
);
error_log("SQL: $sql");
error_log("Параметры: " . json_encode($params));
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$inserted++;
error_log("✓ Строка успешно вставлена, ID: " . $this->pdo->lastInsertId());
} catch (\PDOException $e) {
$errors++;
$errorMessages[] = "Строка " . ($index + 1) . ": " . $this->formatPDOError($e, $schema, $table);
$errorMsg = "Строка " . ($index + 1) . ": " . $this->formatPDOError($e, $schema, $table);
error_log("✗ ОШИБКА при вставке строки " . ($index + 1) . ": " . $e->getMessage());
error_log(" SQL Code: " . $e->getCode());
error_log(" SQL State: " . ($e->errorInfo[0] ?? 'unknown'));
$errorMessages[] = $errorMsg;
}
}
$this->pdo->commit();
error_log("=== ИМПОРТ ЗАВЕРШЁН: вставлено=$inserted, ошибок=$errors ===");
} catch (\Exception $e) {
$this->pdo->rollBack();
error_log("=== КРИТИЧЕСКАЯ ОШИБКА ИМПОРТА: " . $e->getMessage() . " ===");
throw new \RuntimeException('Import failed: ' . $e->getMessage());
}