|
|
@@ -0,0 +1,240 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace Longyi\Wholesale\Console\Commands;
|
|
|
+
|
|
|
+use Illuminate\Console\Command;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+
|
|
|
+/**
|
|
|
+ * ImportMessageData
|
|
|
+ *
|
|
|
+ * 将 Database/message.sql 中的 message 表数据导入到现有 message 表。
|
|
|
+ *
|
|
|
+ * - 清空现有表(TRUNCATE)
|
|
|
+ * - 解析 SQL 文件中的 INSERT 语句(跳过 DROP/CREATE/注释等)
|
|
|
+ * - 将数据映射到现有表结构(email 小写)
|
|
|
+ * - 批量导入
|
|
|
+ *
|
|
|
+ * 用法:php artisan wholesale:import-message [--file=...] [--batch=500]
|
|
|
+ */
|
|
|
+class ImportMessageData extends Command
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * The name and signature of the console command.
|
|
|
+ */
|
|
|
+ protected $signature = 'wholesale:import-message
|
|
|
+ {--file= : SQL 文件路径,默认使用包内 Database/message.sql}
|
|
|
+ {--batch=500 : 每次批量插入的行数}
|
|
|
+ {--truncate : 导入前清空现有表(默认开启,传 --no-truncate 可关闭)}';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The console command description.
|
|
|
+ */
|
|
|
+ protected $description = 'Import message data from message.sql into the message table';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Execute the console command.
|
|
|
+ */
|
|
|
+ public function handle(): int
|
|
|
+ {
|
|
|
+ $file = $this->option('file') ?: __DIR__.'/../../Database/message.sql';
|
|
|
+ $batch = (int) $this->option('batch');
|
|
|
+
|
|
|
+ if (! file_exists($file)) {
|
|
|
+ $this->error("SQL file not found: {$file}");
|
|
|
+
|
|
|
+ return self::FAILURE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 清空现有表
|
|
|
+ DB::table('message')->truncate();
|
|
|
+ $this->info('Existing message table truncated.');
|
|
|
+
|
|
|
+ // 2. 解析 INSERT 语句
|
|
|
+ $this->info('Parsing SQL file...');
|
|
|
+ $rows = $this->parseInserts($file);
|
|
|
+
|
|
|
+ if (empty($rows)) {
|
|
|
+ $this->error('No INSERT statements found in SQL file.');
|
|
|
+
|
|
|
+ return self::FAILURE;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->info("Found {$this->formatNumber(count($rows))} records.");
|
|
|
+
|
|
|
+ // 3. 批量导入
|
|
|
+ $this->info('Importing...');
|
|
|
+ $bar = $this->output->createProgressBar(count($rows));
|
|
|
+ $bar->start();
|
|
|
+
|
|
|
+ $columns = ['message_id', 'name', 'email', 'number', 'message', 'country', 'created_at', 'business_name', 'business_role'];
|
|
|
+
|
|
|
+ foreach (array_chunk($rows, $batch) as $chunk) {
|
|
|
+ DB::table('message')->insert($chunk);
|
|
|
+ $bar->advance(count($chunk));
|
|
|
+ }
|
|
|
+
|
|
|
+ $bar->finish();
|
|
|
+ $this->newLine();
|
|
|
+ $this->info('Import completed successfully.');
|
|
|
+
|
|
|
+ return self::SUCCESS;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析 SQL 文件,提取所有 INSERT 语句的数据行。
|
|
|
+ *
|
|
|
+ * @return array<int, array<string, mixed>>
|
|
|
+ */
|
|
|
+ private function parseInserts(string $file): array
|
|
|
+ {
|
|
|
+ $columns = ['message_id', 'name', 'email', 'number', 'message', 'country', 'created_at', 'business_name', 'business_role'];
|
|
|
+
|
|
|
+ $rows = [];
|
|
|
+
|
|
|
+ $handle = fopen($file, 'r');
|
|
|
+
|
|
|
+ if ($handle === false) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ while (($line = fgets($handle)) !== false) {
|
|
|
+ $line = trim($line);
|
|
|
+
|
|
|
+ // 只处理 INSERT INTO 语句
|
|
|
+ if (! preg_match('/^INSERT\s+INTO\s+`message`\s+VALUES\s*\((.*)\);\s*$/i', $line, $matches)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $values = $this->parseValues($matches[1]);
|
|
|
+
|
|
|
+ if ($values === null) {
|
|
|
+ $this->warn('Skipping malformed INSERT line: '.substr($line, 0, 80).'...');
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 列数校验:9 列(message_id, name, email, number, message, country, created_at, business_name, business_role)
|
|
|
+ if (count($values) !== count($columns)) {
|
|
|
+ $this->warn('Skipping INSERT with unexpected column count ('.count($values).'): '.substr($line, 0, 80).'...');
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $row = array_combine($columns, $values);
|
|
|
+
|
|
|
+ // 类型处理:message_id 转 int,created_at 空字符串转 null
|
|
|
+ $row['message_id'] = (int) $row['message_id'];
|
|
|
+ if ($row['created_at'] === '') {
|
|
|
+ $row['created_at'] = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ $rows[] = $row;
|
|
|
+ }
|
|
|
+
|
|
|
+ fclose($handle);
|
|
|
+
|
|
|
+ return $rows;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析 INSERT VALUES 括号内的内容,返回数组。
|
|
|
+ * 支持单引号转义(\')和 \r\n 等换行符。
|
|
|
+ *
|
|
|
+ * @return array<int, string|null>|null
|
|
|
+ */
|
|
|
+ private function parseValues(string $valuesString): ?array
|
|
|
+ {
|
|
|
+ $values = [];
|
|
|
+ $current = '';
|
|
|
+ $inString = false;
|
|
|
+ $length = strlen($valuesString);
|
|
|
+
|
|
|
+ for ($i = 0; $i < $length; $i++) {
|
|
|
+ $char = $valuesString[$i];
|
|
|
+
|
|
|
+ if ($char === "'") {
|
|
|
+ // 处理转义的单引号 \'
|
|
|
+ if ($inString && $i + 1 < $length && $valuesString[$i + 1] === "'") {
|
|
|
+ $current .= "'";
|
|
|
+ $i++;
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($inString) {
|
|
|
+ // 字符串结束
|
|
|
+ $inString = false;
|
|
|
+ } else {
|
|
|
+ // 字符串开始
|
|
|
+ $inString = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($char === '\\' && $inString && $i + 1 < $length) {
|
|
|
+ // 转义字符处理(\n \r \' 等)
|
|
|
+ $next = $valuesString[$i + 1];
|
|
|
+ $current .= $this->unescape($next);
|
|
|
+ $i++;
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($char === ',' && ! $inString) {
|
|
|
+ // 字段分隔
|
|
|
+ $values[] = $this->normalizeValue($current);
|
|
|
+ $current = '';
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $current .= $char;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最后一个字段
|
|
|
+ if ($current !== '' || count($values) < 9) {
|
|
|
+ $values[] = $this->normalizeValue($current);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $values;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理转义字符。
|
|
|
+ */
|
|
|
+ private function unescape(string $char): string
|
|
|
+ {
|
|
|
+ $map = [
|
|
|
+ 'n' => "\n",
|
|
|
+ 'r' => "\r",
|
|
|
+ 't' => "\t",
|
|
|
+ '\\' => '\\',
|
|
|
+ "'" => "'",
|
|
|
+ '0' => "\0",
|
|
|
+ ];
|
|
|
+
|
|
|
+ return $map[$char] ?? $char;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 规范化字段值:NULL → null,去掉首尾空白。
|
|
|
+ */
|
|
|
+ private function normalizeValue(string $value): ?string
|
|
|
+ {
|
|
|
+ $value = trim($value);
|
|
|
+
|
|
|
+ if (strtoupper($value) === 'NULL') {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $value;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化数字(千分位)。
|
|
|
+ */
|
|
|
+ private function formatNumber(int $number): string
|
|
|
+ {
|
|
|
+ return number_format($number);
|
|
|
+ }
|
|
|
+}
|