*/ private array $fieldLabels = [ 'id' => 'ID', 'user_id' => 'playX-ID', 'date' => '业务日期', 'username' => '用户名', 'yesterday_win_loss_net' => '昨日净输赢', 'yesterday_total_deposit' => '昨日总充值', 'lifetime_total_deposit' => '历史总充值', 'lifetime_total_withdraw' => '历史总提现', 'create_time' => '创建时间', ]; /** * @var array */ private array $fieldLabelsEn = [ 'id' => 'ID', 'user_id' => 'PlayX user ID', 'date' => 'Business date', 'username' => 'Username', 'yesterday_win_loss_net' => 'Yesterday net win/loss', 'yesterday_total_deposit' => 'Yesterday total deposit', 'lifetime_total_deposit' => 'Lifetime total deposit', 'lifetime_total_withdraw' => 'Lifetime total withdraw', 'create_time' => 'Created at', ]; /** * @param array $fields */ public function export( MallDailyPush $model, array $where, array $alias, array $order, array $fields, int $limit, string $lang = 'zh-cn' ): Response { $fields = $this->normalizeFields($fields); if ($fields === []) { throw new \InvalidArgumentException(__('Parameter error')); } $limit = max(1, min(self::MAX_EXPORT_LIMIT, $limit)); $exportDir = runtime_path('export'); if (!is_dir($exportDir) && !mkdir($exportDir, 0755, true) && !is_dir($exportDir)) { throw new \RuntimeException('Failed to create export directory'); } $filepath = $exportDir . DIRECTORY_SEPARATOR . 'daily_push_' . date('YmdHis') . '_' . bin2hex(random_bytes(4)) . '.csv'; $labels = $this->resolveFieldLabels($lang); $handle = fopen($filepath, 'wb'); if ($handle === false) { throw new \RuntimeException('Failed to create export file'); } try { fwrite($handle, "\xEF\xBB\xBF"); fputcsv($handle, $this->buildHeaderRow($fields, $labels)); $exported = 0; $page = 1; while ($exported < $limit) { $batchSize = min(self::CHUNK_SIZE, $limit - $exported); $rows = $model ->field($fields) ->alias($alias) ->where($where) ->order($order) ->page($page, $batchSize) ->select(); if ($rows->isEmpty()) { break; } foreach ($rows as $row) { fputcsv($handle, $this->formatRow($row->toArray(), $fields)); $exported++; if ($exported >= $limit) { break; } } if ($rows->count() < $batchSize) { break; } $page++; } } catch (Throwable $e) { fclose($handle); if (is_file($filepath)) { @unlink($filepath); } throw $e; } fclose($handle); register_shutdown_function(static function () use ($filepath): void { if (is_file($filepath)) { @unlink($filepath); } }); $filename = 'daily_push_' . date('YmdHis') . '.csv'; return (new Response())->file($filepath, $filename); } /** * @param array $fields * @return array */ public function normalizeFields(array $fields): array { $allowed = array_keys($this->fieldLabels); $normalized = []; foreach ($fields as $field) { $field = trim(strval($field)); if ($field !== '' && in_array($field, $allowed, true) && !in_array($field, $normalized, true)) { $normalized[] = $field; } } return $normalized; } /** * @return array */ public function getDefaultFields(): array { return array_keys($this->fieldLabels); } public function getMaxExportLimit(): int { return self::MAX_EXPORT_LIMIT; } /** * @param array $fields * @param array $labels * @return array */ private function buildHeaderRow(array $fields, array $labels): array { $header = []; foreach ($fields as $field) { $header[] = $labels[$field] ?? $field; } return $header; } /** * @param array $row * @param array $fields * @return array */ private function formatRow(array $row, array $fields): array { $line = []; foreach ($fields as $field) { $value = $row[$field] ?? ''; if ($field === 'create_time' && $value !== '' && $value !== null) { $timestamp = is_numeric($value) ? intval($value) : strtotime(strval($value)); $line[] = $timestamp > 0 ? date('Y-m-d H:i:s', $timestamp) : ''; continue; } $line[] = $value === null ? '' : strval($value); } return $line; } /** * @return array */ private function resolveFieldLabels(string $lang): array { $lang = strtolower(str_replace('_', '-', $lang)); return $lang === 'en' ? $this->fieldLabelsEn : $this->fieldLabels; } }