Files
webman-buildadmin-mall/app/common/library/MallDailyPushExport.php

225 lines
6.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\common\library;
use app\common\model\MallDailyPush;
use OpenSpout\Common\Entity\Row;
use OpenSpout\Writer\XLSX\Options;
use OpenSpout\Writer\XLSX\Writer;
use support\Response;
use Throwable;
/**
* 每日推送数据导出OpenSpout 流式 XLSX
*/
class MallDailyPushExport
{
/** 0 表示不限制条数,导出全部匹配数据 */
public const UNLIMITED_EXPORT = 0;
public const CHUNK_SIZE = 2000;
/**
* @var array<string, string>
*/
private array $fieldLabels = [
'id' => 'ID',
'user_id' => 'playX-ID',
'date' => '业务日期',
'username' => '用户名',
'yesterday_win_loss_net' => '昨日净输赢',
'converted_points' => '转换积分',
'yesterday_total_deposit' => '昨日总充值',
'lifetime_total_deposit' => '终身总充值',
'lifetime_total_withdraw' => '终身总提现',
'lifetime_win_loss_net' => '终身总输赢',
'create_time' => '创建时间',
];
/**
* @var array<string, string>
*/
private array $fieldLabelsEn = [
'id' => 'ID',
'user_id' => 'PlayX user ID',
'date' => 'Business date',
'username' => 'Username',
'yesterday_win_loss_net' => 'Yesterday net win/loss',
'converted_points' => 'Converted points',
'yesterday_total_deposit' => 'Yesterday total deposit',
'lifetime_total_deposit' => 'Lifetime total deposit',
'lifetime_total_withdraw' => 'Lifetime total withdraw',
'lifetime_win_loss_net' => 'Lifetime net win/loss',
'create_time' => 'Created at',
];
/**
* @param array<int, string> $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'));
}
if ($limit < 0) {
throw new \InvalidArgumentException(__('Parameter error'));
}
@set_time_limit(0);
$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)) . '.xlsx';
$labels = $this->resolveFieldLabels($lang);
$options = new Options();
$options->setTempFolder($exportDir);
$writer = new Writer($options);
try {
$writer->openToFile($filepath);
$writer->addRow(Row::fromValues($this->buildHeaderRow($fields, $labels)));
$exported = 0;
$page = 1;
$unlimited = $limit === self::UNLIMITED_EXPORT;
while ($unlimited || $exported < $limit) {
$batchSize = $unlimited ? self::CHUNK_SIZE : 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) {
$writer->addRow(Row::fromValues($this->formatRow($row->toArray(), $fields)));
$exported++;
if (!$unlimited && $exported >= $limit) {
break;
}
}
if ($rows->count() < $batchSize) {
break;
}
$page++;
}
$writer->close();
} catch (Throwable $e) {
try {
$writer->close();
} catch (Throwable) {
// ignore
}
if (is_file($filepath)) {
@unlink($filepath);
}
throw $e;
}
register_shutdown_function(static function () use ($filepath): void {
if (is_file($filepath)) {
@unlink($filepath);
}
});
$filename = 'daily_push_' . date('YmdHis') . '.xlsx';
return (new Response())->file($filepath, $filename);
}
/**
* @param array<int, string> $fields
* @return array<int, string>
*/
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<int, string>
*/
public function getDefaultFields(): array
{
return array_keys($this->fieldLabels);
}
public function getMaxExportLimit(): int
{
return self::UNLIMITED_EXPORT;
}
/**
* @param array<int, string> $fields
* @param array<string, string> $labels
* @return array<int, string>
*/
private function buildHeaderRow(array $fields, array $labels): array
{
$header = [];
foreach ($fields as $field) {
$header[] = $labels[$field] ?? $field;
}
return $header;
}
/**
* @param array<string, mixed> $row
* @param array<int, string> $fields
* @return array<int, string>
*/
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<string, string>
*/
private function resolveFieldLabels(string $lang): array
{
$lang = strtolower(str_replace('_', '-', $lang));
return $lang === 'en' ? $this->fieldLabelsEn : $this->fieldLabels;
}
}