From 08e2b883f2be1d1bcdc17957863968b2fa8e89ab Mon Sep 17 00:00:00 2001
From: vartrue <1175398345@qq.com>
Date: Fri, 12 Jul 2024 12:00:22 +0800
Subject: [PATCH] feat:v1.0
---
.editorconfig | 20 ++
.gitattributes | 13 +
.gitignore | 11 +
.php-cs-fixer.php | 49 +++
LICENSE | 21 ++
README.md | 53 +++
composer.json | 67 ++++
phpunit.xml.dist | 21 ++
src/ExcelAbstract.php | 536 +++++++++++++++++++++++++++++
src/ExcelManager.php | 48 +++
src/ProgressTrait.php | 316 +++++++++++++++++
src/behaviors/ExcelLogBehavior.php | 89 +++++
src/data/export/Column.php | 69 ++++
src/data/export/ExportConfig.php | 194 +++++++++++
src/data/export/ExportData.php | 52 +++
src/data/export/Sheet.php | 150 ++++++++
src/data/import/ImportConfig.php | 190 ++++++++++
src/data/import/ImportData.php | 60 ++++
src/data/import/Sheet.php | 145 ++++++++
src/drivers/csv/Excel.php | 29 ++
src/drivers/xlswriter/Excel.php | 189 ++++++++++
src/events/ErrorEvent.php | 29 ++
src/events/ExportEvent.php | 20 ++
src/events/ImportEvent.php | 18 +
src/events/ProgressEvent.php | 21 ++
src/exceptions/ExcelException.php | 10 +
src/jobs/ExportJob.php | 34 ++
src/jobs/ImportJob.php | 36 ++
src/jobs/base/BaseJob.php | 28 ++
src/utils/Helper.php | 72 ++++
tests/ExportTest.php | 79 +++++
tests/ImportTest.php | 41 +++
tests/config/TestExportConfig.php | 99 ++++++
tests/config/TestImportConfig.php | 38 ++
tests/files/ceshi.xlsx | Bin 0 -> 8697 bytes
35 files changed, 2847 insertions(+)
create mode 100644 .editorconfig
create mode 100644 .gitattributes
create mode 100644 .gitignore
create mode 100644 .php-cs-fixer.php
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 composer.json
create mode 100644 phpunit.xml.dist
create mode 100644 src/ExcelAbstract.php
create mode 100644 src/ExcelManager.php
create mode 100644 src/ProgressTrait.php
create mode 100644 src/behaviors/ExcelLogBehavior.php
create mode 100644 src/data/export/Column.php
create mode 100644 src/data/export/ExportConfig.php
create mode 100644 src/data/export/ExportData.php
create mode 100644 src/data/export/Sheet.php
create mode 100644 src/data/import/ImportConfig.php
create mode 100644 src/data/import/ImportData.php
create mode 100644 src/data/import/Sheet.php
create mode 100644 src/drivers/csv/Excel.php
create mode 100644 src/drivers/xlswriter/Excel.php
create mode 100644 src/events/ErrorEvent.php
create mode 100644 src/events/ExportEvent.php
create mode 100644 src/events/ImportEvent.php
create mode 100644 src/events/ProgressEvent.php
create mode 100644 src/exceptions/ExcelException.php
create mode 100644 src/jobs/ExportJob.php
create mode 100644 src/jobs/ImportJob.php
create mode 100644 src/jobs/base/BaseJob.php
create mode 100644 src/utils/Helper.php
create mode 100644 tests/ExportTest.php
create mode 100644 tests/ImportTest.php
create mode 100644 tests/config/TestExportConfig.php
create mode 100644 tests/config/TestImportConfig.php
create mode 100644 tests/files/ceshi.xlsx
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..df55cd7
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = false
+
+[*.{vue,js,scss}]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..9e631da
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,13 @@
+* text=auto
+
+/tests export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
+.scrutinizer.yml export-ignore
+.travis.yml export-ignore
+phpunit.php export-ignore
+phpunit.xml.dist export-ignore
+phpunit.xml export-ignore
+.php_cs export-ignore
+.php-cs-fixer.cache
+.phpunit.result.cache
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0a68851
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+.idea
+*.DS_Store
+/vendor
+/coverage
+/hooks
+sftp-config.json
+composer.lock
+.subsplit
+.php_cs.cache
+cghooks.lock
+
diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
new file mode 100644
index 0000000..8695228
--- /dev/null
+++ b/.php-cs-fixer.php
@@ -0,0 +1,49 @@
+setRules([
+ '@PSR12' => true,
+ 'binary_operator_spaces' => true,
+ 'blank_line_after_opening_tag' => true,
+ 'compact_nullable_typehint' => true,
+ 'declare_equal_normalize' => true,
+ 'lowercase_cast' => true,
+ 'lowercase_static_reference' => true,
+ 'new_with_braces' => true,
+ 'no_blank_lines_after_class_opening' => true,
+ 'no_leading_import_slash' => true,
+ 'no_whitespace_in_blank_line' => true,
+ 'no_unused_imports' => true,
+ 'ordered_class_elements' => [
+ 'order' => [
+ 'use_trait',
+ ],
+ ],
+ 'ordered_imports' => [
+ 'imports_order' => [
+ 'class',
+ 'function',
+ 'const',
+ ],
+ 'sort_algorithm' => 'none',
+ ],
+ 'return_type_declaration' => true,
+ 'short_scalar_cast' => true,
+ 'single_blank_line_before_namespace' => true,
+ 'single_trait_insert_per_statement' => true,
+ 'ternary_operator_spaces' => true,
+ 'unary_operator_spaces' => true,
+ 'visibility_required' => [
+ 'elements' => [
+ 'const',
+ 'method',
+ 'property',
+ ],
+ ],
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->exclude('vendor')
+ ->in([__DIR__.'/src/', __DIR__.'/tests/'])
+ )
+;
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7a022a3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 vartruexuan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f564a93
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+# yii-excel
+
+[![php](https://img.shields.io/badge/php-%3E=8.2-brightgreen.svg?maxAge=2592000)](https://github.com/php/php-src)
+[![Latest Stable Version](https://img.shields.io/packagist/v/vartruexuan/yii-excel)](https://packagist.org/packages/vartruexuan/yii-excel)
+[![Total Downloads](https://img.shields.io/packagist/dt/vartruexuan/yii-excel)](https://packagist.org/packages/vartruexuan/yii-excel)
+[![License](https://img.shields.io/packagist/l/vartruexuan/yii-excel)](https://github.com/vartruexuan/yii-excel)
+
+# 概述
+excel 导入导出,相关文档处理组件
+
+## 组件能力
+
+- [x] 导入、导出excel
+- [x] 支持异步操作,进度构建,信息构建
+- [x] 支持 `xlswriter`
+- [ ] 支持 `csv`
+- [ ] ...
+# 安装
+```shell
+composer require vartruexuan/yii-excel
+```
+## 配置
+
+## 使用
+### 配置
+- 配置组件 `components`
+```php
+[
+ 'components' => [
+ // excel组件
+ 'excel' => [
+ 'class' => \vartruexuan\excel\drivers\xlswriter\Excel::class,
+ 'fileSystem' => 'filesystem', // 文件管理组件
+ 'redis' => 'redis', // redis
+ 'queue' => 'queue', // 队列组件
+ // 日志行为类
+ 'as log' => \vartruexuan\excel\behaviors\ExcelLogBehavior::class
+ ]
+ ]
+]
+```
+### 导出
+```php
+
+```
+### 导入
+
+```php
+
+```
+## License
+
+MIT
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..a97ce0a
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,67 @@
+{
+ "name": "vartruexuan/yii-excel",
+ "description": "excel导入导出组件",
+ "homepage": "https://github.com/vartruexuan/yii-excel",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "guozhaoxuan",
+ "email": "guozhaoxuanx@163.com"
+ }
+ ],
+ "require": {
+ "php": ">=8.2",
+ "overtrue/http": "^1.2",
+ "creocoder/yii2-flysystem": "^1.1",
+ "ramsey/uuid": "*"
+ },
+ "require-dev": {
+ "brainmaestro/composer-git-hooks": "^2.8",
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "mockery/mockery": "^1.2",
+ "phpunit/phpunit": "^9.0",
+ "vimeo/psalm": "^4.10",
+ "jetbrains/phpstorm-attributes": "^1.0"
+ },
+ "autoload": {
+ "files": [
+ ],
+ "psr-4": {
+ "vartruexuan\\excel\\": "src"
+ }
+ },
+ "extra": {
+ "hooks": {
+ "pre-commit": [
+ "composer check-style",
+ "composer test"
+ ],
+ "pre-push": [
+ "composer test",
+ "composer check-style"
+ ]
+ },
+ "vartruexuan": {
+ "config": "vartruexuan\\excel\\ConfigProvider"
+ }
+ },
+ "scripts": {
+ "post-update-cmd": [
+ ],
+ "post-merge": "",
+ "post-install-cmd": [
+ ],
+ "cghooks": "vendor/bin/cghooks",
+ "check-style": "php-cs-fixer fix --using-cache=no --diff --dry-run --ansi",
+ "fix-style": "php-cs-fixer fix --using-cache=no --ansi",
+ "test": "phpunit --colors",
+ "psalm": "psalm --show-info=true --no-cache",
+ "psalm-fix": "psalm --no-cache --alter --issues=MissingReturnType,MissingParamType"
+ },
+ "scripts-descriptions": {
+ "test": "Run all tests.",
+ "check-style": "Run style checks (only dry run - no fixing!).",
+ "fix-style": "Run style checks and fix violations."
+ }
+
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..e47284c
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,21 @@
+
+
+
+
+ ./tests/
+
+
+
+
+ src/
+
+
+
diff --git a/src/ExcelAbstract.php b/src/ExcelAbstract.php
new file mode 100644
index 0000000..72697f9
--- /dev/null
+++ b/src/ExcelAbstract.php
@@ -0,0 +1,536 @@
+redis instanceof Connection) {
+ $this->redis = Instance::ensure($this->redis, '\yii\redis\Connection');
+ }
+
+ if (!$this->queue instanceof Queue) {
+ $this->queue = Instance::ensure($this->queue, '\yii\queue\Queue');
+ }
+
+ if (!$this->fileSystem instanceof Filesystem) {
+ $this->fileSystem = Instance::ensure($this->fileSystem, '\creocoder\flysystem\Filesystem');
+ }
+ }
+
+ /**
+ * 导出
+ *
+ * @param ExportConfig $config 导出配置
+ *
+ * @return string 返回文件地址
+ */
+ abstract protected function exportExcel(ExportConfig $config): string;
+
+ /**
+ * 导入操作
+ *
+ * @param ImportConfig $config 导入配置
+ * @return ImportData 导入数据
+ */
+ abstract protected function importExcel(ImportConfig $config): ImportData;
+
+
+ /**
+ * 导出
+ *
+ * @param ExportConfig $config
+ * @return void
+ */
+ public function export(ExportConfig $config)
+ {
+ $config = $this->formatExportConfig($config);
+ try {
+ $token = $config->getToken();
+ $exportData = new ExportData([
+ 'token' => $token,
+ ]);
+
+ $event = new ExportEvent([
+ 'exportConfig' => $config,
+ ]);
+ $this->trigger(self::ENVENT_BEFORE_EXPORT, $event);
+
+ $this->initProgressInfo($token);
+ // 异步
+ if ($config->getIsAsync()) {
+ // 异步不支持直接输出
+ if ($config->getOutPutType() == ExportConfig::OUT_PUT_TYPE_OUT) {
+ throw new ExcelException('Async does not support output type ExportConfig::OUT_PUT_TYPE_OUT');
+ }
+ $exportData->queueId = $this->pushExportQueue($config->setToken($token));
+ return $exportData;
+ }
+ $filePath = $this->exportExcel($config);
+
+ // 文件输出
+ if ($config->outPutType == ExportConfig::OUT_PUT_TYPE_UPLOAD) {
+ // 上传文件
+ if (!$this->fileSystem->writeStream($config->path, fopen($filePath, 'r+'))) {
+ throw new ExcelException('upload file fail');
+ }
+ if (method_exists($config, 'getUrl')) {
+ $url = $config->getUrl($config->path);
+ } else {
+ $url = $this->getFileSystemUrl($config->path);
+ }
+ $exportData->setPath($url);
+ $this->setProgressInfo($token, null, self::PROGRESS_STATUS_END, [
+ 'url' => $url,
+ ]);
+ } else if ($config->outPutType == ExportConfig::OUT_PUT_TYPE_LOCAL) {
+ if (copy($filePath, $config->getPath()) === false) {
+ throw new ExcelException('copy file fail');
+ }
+ $exportData->setPath($config->getPath());
+ $this->setProgressInfo($token, null, self::PROGRESS_STATUS_END);
+ } else {
+ // Set Header
+ header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ header('Content-Disposition: attachment;filename="' . urlencode($config->getFileName()) . '"');
+ header('Content-Length: ' . filesize($filePath));
+ header('Content-Transfer-Encoding: binary');
+ header('Cache-Control: must-revalidate');
+ header('Cache-Control: max-age=0');
+ header('Pragma: public');
+
+ ob_clean();
+ flush();
+ // 直接输出
+ if (copy($filePath, 'php://output') === false) {
+ throw new ExcelException('copy file fail');
+ }
+ }
+ $this->trigger(self::ENVENT_AFTER_EXPORT, $event);
+ // 删除临时文件
+ @$this->deleteFile($filePath);
+ $this->setProgressInfo($token, null, self::PROGRESS_STATUS_END);
+ } catch (ExcelException $excelException) {
+ // 失败
+ $this->setProgressInfo($token, null, self::PROGRESS_STATUS_FAIL);
+ $this->setProgressMessage($token, $excelException->getMessage());
+ $this->trigger(self::EVENT_ERROR, new ErrorEvent([
+ 'config' => $config,
+ 'exception' => $excelException,
+ ]));
+ throw $excelException;
+ } catch (\Throwable $exception) {
+ $this->setProgressInfo($token, null, self::PROGRESS_STATUS_FAIL);
+ $this->setProgressMessage($token, '导出异常');
+ $this->trigger(self::EVENT_ERROR, new ErrorEvent([
+ 'config' => $config,
+ 'exception' => $exception,
+ ]));
+ \Yii::error($exception->getMessage(), 'vartruexuan/excel/export');
+ throw $exception;
+ }
+
+ return $exportData;
+ }
+
+ /**
+ * 导入
+ *
+ * @param ImportConfig $config
+ * @return void
+ */
+ public function import(ImportConfig $config)
+ {
+ $config = $this->formatImportConfig($config);
+ try {
+ $token = $config->getToken();
+ $importData = new ImportData([
+ 'token' => $token,
+ ]);
+
+ $event = new ImportEvent([
+ 'importConfig' => $config,
+ ]);
+ $this->trigger(self::ENVENT_BEFORE_IMPORT, $event);
+
+ $this->initProgressInfo($token);
+ // 异步
+ if ($config->getIsAsync()) {
+ $importData->queueId = $this->pushImportQueue($config->setToken($token));
+ return $importData;
+ }
+
+ // 构建临时文件
+ $filePath = $this->getTempFileName();
+ $config->setTempPath($filePath);
+ if (!$this->isRemoteUrl($config->getPath())) {
+ // 本地文件
+ if (!is_file($config->getPath())) {
+ throw new ExcelException('path not exists');
+ }
+ if (!FileHelper::copy($config->getPath(), $filePath)) {
+ throw new ExcelException('file copy error');
+ }
+ } else {
+ // 远程文件
+ if (!$this->downloadFile($config->getPath(), $filePath)) {
+ throw new ExcelException('file download error');
+ }
+ }
+
+ // 执行导入
+ $importData = $this->importExcel($config);
+
+ $this->trigger(self::ENVENT_AFTER_IMPORT, $event);
+
+ $this->setProgressInfo($token, null, self::PROGRESS_STATUS_END);
+
+ } catch (ExcelException $excelException) {
+ // 失败
+ $this->setProgressInfo($token, null, self::PROGRESS_STATUS_FAIL);
+ $this->setProgressMessage($token, $excelException->getMessage());
+ $this->trigger(self::EVENT_ERROR, new ErrorEvent([
+ 'config' => $config,
+ 'exception' => $excelException,
+ ]));
+ throw $excelException;
+ } catch (\Throwable $exception) {
+ $this->setProgressInfo($token, null, self::PROGRESS_STATUS_FAIL);
+ $this->setProgressMessage($token, '导入异常');
+ $this->trigger(self::EVENT_ERROR, new ErrorEvent([
+ 'config' => $config,
+ 'exception' => $exception,
+ ]));
+ \Yii::error($exception->getMessage(), 'vartruexuan/excel/import');
+ throw $exception;
+ }
+
+ return $importData;
+ }
+
+
+ /**
+ * 导入异步队列
+ *
+ * @param ImportConfig $config
+ * @return string|null
+ */
+ protected function pushImportQueue(ImportConfig $config)
+ {
+ return $this->queue->push(new ImportJob([
+ 'importConfig' => $config,
+ 'componentId' => $this->getCommandId(),
+ ]));
+ }
+
+ /**
+ * 导出异步队列
+ *
+ * @param ExportConfig $config
+ * @return string|null
+ */
+ protected function pushExportQueue(ExportConfig $config)
+ {
+ return $this->queue->push(new ExportJob([
+ 'exportConfig' => $config,
+ 'componentId' => $this->getCommandId(),
+ ]));
+ }
+
+
+ /**
+ * 格式化导入配置数据
+ *
+ * @param ImportConfig $config
+ * @return ImportConfig
+ */
+ protected function formatImportConfig(ImportConfig $config)
+ {
+ if (!$config->getToken()) {
+ $config->setToken($this->buildToken());
+ }
+ // 格式化页码
+ $sheets = $config->getSheets();
+ foreach ($sheets as &$sheet) {
+ if (!$sheet instanceof Sheet) {
+ $sheet = new Sheet($sheet);
+ }
+ }
+ $config->setSheets($sheets);
+ return $config;
+ }
+
+ /**
+ * 格式化导出配置
+ *
+ * @param ExportConfig $config
+ * @return ExportConfig
+ */
+ protected function formatExportConfig(ExportConfig $config)
+ {
+
+ if (!$config->getToken()) {
+ $config->setToken($this->buildToken());
+ }
+ return $config;
+ }
+
+
+ /**
+ * 获取临时目录
+ *
+ * @return string
+ */
+ protected function getTempDir()
+ {
+ return sys_get_temp_dir();
+ }
+
+ /**
+ * 构建一个临时文件
+ *
+ * @return false|string
+ */
+ protected function getTempFileName($prefix = 'ex_')
+ {
+ return tempnam($this->getTempDir(), $prefix);
+ }
+
+
+ /**
+ * 删除文件
+ *
+ * @param $filepath
+ * @return bool
+ */
+ protected function deleteFile($filepath)
+ {
+ return Helper::instance()->deleteFile($filepath);
+ }
+
+ /**
+ * 远程文件地址
+ *
+ * @param $remotePath
+ * @param $filepath
+ * @return void
+ */
+ protected function downloadFile($remotePath, $filePath)
+ {
+ return Helper::instance()->downloadFile($remotePath, $filePath);
+ }
+
+ /**
+ * 是否是url地址
+ *
+ * @param $url
+ * @return void
+ */
+ protected function isRemoteUrl($url)
+ {
+ return Helper::instance()->isUrl($url);
+ }
+
+ /**
+ * 构建token
+ *
+ * @return string
+ * @throws \yii\base\Exception
+ */
+ protected function buildToken()
+ {
+ // return \Yii::$app->security->generateRandomString();
+ return Helper::instance()->uuid4();
+ }
+
+ /**
+ * 行回调
+ *
+ * @param callable $callback
+ * @param array $row
+ * @param ExcelAbstract $excel
+ * @param ImportConfig $config
+ * @param string $sheetName
+ * @param bool $isSetProgress
+ * @param int $status
+ * @return mixed
+ */
+ protected function importRowCallback(callable $callback, array $row, ExcelAbstract $excel, ImportConfig $config, string $sheetName, $isSetProgress = true, $status = self::PROGRESS_STATUS_PROCESS)
+ {
+ $errorMessage = null;
+ try {
+ $result = call_user_func($callback, $row, $excel, $config);
+ } catch (\Throwable $exception) {
+ $errorMessage = $exception->getMessage();
+ }
+ // 设置进度信息
+ if ($isSetProgress) {
+ $prgress = 1;
+ if (!$errorMessage) {
+ $success = 1;
+ } else {
+ $fail = 1;
+ }
+ $this->setSheetProgress($config->getToken(), $sheetName, $status, $prgress ?? 0, $success ?? 0, $fail ?? 0);
+ }
+ return $result ?? null;
+ }
+
+
+ /**
+ * 导出数据回调
+ *
+ * @param callable $callback 回调
+ * @param int $page 页码
+ * @param int $pageCount 限制每页数量
+ * @param ?int $count 总数
+ * @param $param 额外参数
+ * @param string $token
+ * @param string $sheetName
+ * @return mixed
+ */
+ protected function exportDataFunc(callable $callback, ExportConfig $config, int $page, int $pageCount, ?int $count, string $sheetName)
+ {
+ return call_user_func($callback, $config, $page, $pageCount, $count, $sheetName, $this);
+ }
+
+
+ /**
+ * 获取命令ID(组件ID)
+ *
+ * @return string command id
+ * @throws
+ */
+ protected function getCommandId()
+ {
+ foreach (\Yii::$app->getComponents(false) as $id => $component) {
+ if ($component === $this) {
+ return Inflector::camel2id($id);
+ }
+ }
+ throw new InvalidConfigException('excel must be an application component.');
+ }
+
+ /**
+ * 获取文件地址
+ *
+ * @param $url
+ * @return void
+ */
+ protected function getFileSystemUrl(string $path, bool $isSign = false)
+ {
+ // cdn
+ if ($this->fileSystem->cdn) {
+ return $this->fileSystem->getAdapter()->applyPathPrefix($path);
+ }
+
+ // 签名地址
+ if ($isSign) {
+ return $this->fileSystem->getAdapter()->getUrl($path);
+ }
+ // 原始地址
+ $scheme = $this->fileSystem->scheme ? $this->fileSystem->scheme : 'http';
+ $sourcePath = $this->fileSystem->getAdapter()->getSourcePath($path);
+ return "{$scheme}://{$sourcePath}";
+ }
+
+}
\ No newline at end of file
diff --git a/src/ExcelManager.php b/src/ExcelManager.php
new file mode 100644
index 0000000..1bd8979
--- /dev/null
+++ b/src/ExcelManager.php
@@ -0,0 +1,48 @@
+has($componentId)) {
+ throw new ExcelException("component id error {$this->componentId}");
+ }
+ $component = \Yii::$app->{$componentId};
+ if (!$component instanceof ExcelAbstract) {
+ throw new ExcelException("component is not ExcelAbstract instance");
+ }
+ return $component;
+ }
+ $defaultDriverClass = $defaultDriverClass ?: $this->getDefaultDriverClass();
+ return $defaultDriverClass::instance();
+ }
+
+ /**
+ * 获取默认驱动
+ *
+ * @return string
+ */
+ public function getDefaultDriverClass()
+ {
+ return '\vartruexuan\excel\drivers\xlswriter\Excel';
+ }
+
+}
\ No newline at end of file
diff --git a/src/ProgressTrait.php b/src/ProgressTrait.php
new file mode 100644
index 0000000..44558ab
--- /dev/null
+++ b/src/ProgressTrait.php
@@ -0,0 +1,316 @@
+setProgressInfo($token, [], self::PROGRESS_STATUS_AWAIT);
+ }
+
+ /**
+ * 设置进度信息
+ *
+ * @param string $token token
+ * @param array|null $sheetList 页信息
+ * @param bool $isAwait 是否在等待处理
+ * @param bool|null $status 进度状态
+ * @param array|null $data 数据
+ * @return mixed
+ */
+ public function setProgressInfo(string $token, ?array $sheetList = null, ?int $status = self::PROGRESS_STATUS_PROCESS, ?array $data = null)
+ {
+ // 初始化进度信息
+ $info = $this->getProgressInfo($token);
+ $info = $info ?? [];
+ $info['status'] = $status;
+ if ($sheetList !== null) {
+ $info['sheetList'] = $sheetList;
+ }
+ if ($data) {
+ $info['data'] = $data;
+ }
+ $this->redis->set($this->getProgressCacheKeyByInfo($token), Json::encode($info));
+ $this->resetProgressTime($token);
+ }
+
+ /**
+ * 获取进度信息
+ *
+ * @param string $token
+ * @return mixed|null
+ */
+ public function getProgressInfo(string $token)
+ {
+ $info = $this->redis->get($this->getProgressCacheKeyByInfo($token));
+ return Json::decode($info, true);
+ }
+
+ /**
+ * 查询进度
+ *
+ * @param $token
+ * @return void
+ */
+ public function getProgress($token)
+ {
+ $info = $this->getProgressInfo($token);
+ if (!$info) {
+ return false;
+ }
+ $progressInfo = [
+ 'sheetList' => [],
+ 'all' => [
+ 'total' => 0,
+ 'progress' => 0,
+ 'success' => 0,
+ 'fail' => 0,
+ 'status' => (int)($info['status'] ?? self::PROGRESS_STATUS_AWAIT),
+ ],
+ 'data' => $info['data'] ?? null,
+ ];
+ foreach ($info['sheetList'] ?? [] as $key => $sheetName) {
+ $sheetProgress = $this->getSheetProgress($token, $sheetName);
+ $progressInfo['sheetList'][$sheetName] = $sheetProgress;
+ $progressInfo['all']['total'] += $sheetProgress['total'] ?? 0;
+ $progressInfo['all']['progress'] += $sheetProgress['progress'] ?? 0;
+ $progressInfo['all']['success'] += $sheetProgress['success'] ?? 0;
+ $progressInfo['all']['fail'] += $sheetProgress['fail'] ?? 0;
+ }
+
+
+ return $progressInfo;
+ }
+
+
+ /**
+ * 初始化进度信息
+ *
+ * @param string $token
+ * @param string $sheetName
+ * @param int $total
+ * @return void
+ */
+ public function initSheetProgress(string $token, string $sheetName, int $total = 0)
+ {
+ $key = $this->getProgressCacheKeyBySheet($token, $sheetName);
+ $this->redis->hmset($key, [
+ 'total' => $total,
+ 'progress' => 0,
+ 'success' => 0,
+ 'fail' => 0,
+ 'status' => self::PROGRESS_STATUS_AWAIT,
+ ]);
+ $this->resetProgressTime($token);
+
+ }
+
+ /**
+ * 设置页码进度
+ *
+ * @param string $token
+ * @param string $sheetName
+ * @param bool $status
+ * @param int $progress
+ * @param int $success
+ * @param int $fail
+ * @return void
+ */
+ public function setSheetProgress(string $token, string $sheetName, ?int $status = null, int $progress = 0, int $success = 0, int $fail = 0)
+ {
+ $key = $this->getProgressCacheKeyBySheet($token, $sheetName);
+ if ($progress > 0) {
+ $this->redis->hincrby($key, 'progress', $progress);
+ }
+ if ($success > 0) {
+ $this->redis->hincrby($key, 'success', $success);
+ }
+ if ($fail > 0) {
+ $this->redis->hincrby($key, 'fail', $fail);
+ }
+ if ($status !== null) {
+ $this->redis->hmset($key, 'status', $status);
+ }
+ $this->resetProgressTime($token);
+
+ $progressInfo = $this->getProgressInfo($token);
+ // 触发事件
+ $this->trigger(ExcelAbstract::EVENT_AFTER_PROGRESS, new ProgressEvent([
+ 'progressInfo' => $progressInfo,
+ 'token' => $token,
+ ]));
+ }
+
+
+ /**
+ * 设置消息(队列方式)
+ *
+ * @return void
+ */
+ public function setProgressMessage($token, $message)
+ {
+ $key = $this->getProgressCacheKeyByMessage($token);
+ $this->redis->lpush($key, $message);
+ $this->resetProgressTime($token);
+ }
+
+ /**
+ * 获取进度消息
+ *
+ * @param $token
+ * @param int $limit 限制数量
+ * @return array
+ */
+ public function getProgressMessage($token, int $limit = 30)
+ {
+ $key = $this->getProgressCacheKeyByMessage($token);
+ $messages = [];
+ for ($i = 0; $i < $limit; $i++) {
+ if ($message = $this->redis->rpop($key)) {
+ $messages[] = $message;
+ }
+ }
+ return $messages;
+ }
+
+ /**
+ * 重置key时间
+ *
+ * @param $key
+ * @param $time
+ * @return mixed
+ */
+ public function resetProgressTime($token, $time = null)
+ {
+ $time = $time ? $time : $this->progressExpireTime;
+ $this->redis->expire($this->getProgressCacheKeyByInfo($token), $time);
+ $this->redis->expire($this->getProgressCacheKeyByMessage($token), $time);
+ $progressInfo = $this->getProgressInfo($token);
+
+ foreach ($progressInfo['sheetList'] ?? [] as $sheetName) {
+ $this->redis->expire($this->getProgressCacheKeyBySheet($token, $sheetName), $time);
+ }
+
+ }
+
+
+ /**
+ * 获取页码进度信息
+ *
+ * @param string $token
+ * @param string $sheetName
+ * @return mixed
+ */
+ public function getSheetProgress(string $token, string $sheetName)
+ {
+ $result = $this->redis->hgetall($this->getProgressCacheKeyBySheet($token, $sheetName));
+ return [
+ 'total' => intval($result['total'] ?? 0),
+ 'progress' => intval($result['progress'] ?? 0),
+ 'success' => intval($result['success'] ?? 0),
+ 'fail' => intval($result['fail'] ?? 0),
+ 'status' => intval($result['status'] ?? self::PROGRESS_STATUS_AWAIT),
+ ];
+ }
+
+
+ /**
+ * 获取进度信息key
+ *
+ * @param $token
+ * @return string
+ */
+ protected function getProgressCacheKeyByInfo($token)
+ {
+ return sprintf(self::PROGRESS_KEY_INFO, $token);
+ }
+
+ /**
+ * 获取页码进度key
+ *
+ * @param $token
+ * @return string
+ */
+ protected function getProgressCacheKeyBySheet($token, $sheetName)
+ {
+ return sprintf(self::PROGRESS_KEY_SHEET, $token, $sheetName);
+ }
+
+ /**
+ * 错误信息
+ *
+ * @param $token
+ * @return string
+ */
+ protected function getProgressCacheKeyByMessage($token)
+ {
+ return sprintf(self::PROGRESS_KEY_MESSAGE, $token);
+ }
+
+ /**
+ * 计算状态
+ *
+ * @param $sheetStatusList
+ * @return void
+ */
+ protected function getAllStatus($sheetStatusList)
+ {
+ $allStatus = self::PROGRESS_STATUS_AWAIT;
+ $sheetStatusList = array_unique($sheetStatusList ?? []);
+ $sheetStatusCount = count($sheetStatusList);
+ if ($sheetStatusCount == 1) {
+ $allStatus = $sheetStatusList[0];
+ } else if ($sheetStatusCount > 1) {
+ if (!in_array(self::PROGRESS_STATUS_FAIL, $sheetStatusList)) {
+ $allStatus = self::PROGRESS_STATUS_FAIL;
+ } else {
+ $allStatus = self::PROGRESS_STATUS_PROCESS;
+ }
+ } else {
+ $allStatus = self::PROGRESS_STATUS_AWAIT;
+ }
+ return $allStatus;
+ }
+
+}
\ No newline at end of file
diff --git a/src/behaviors/ExcelLogBehavior.php b/src/behaviors/ExcelLogBehavior.php
new file mode 100644
index 0000000..8ad4ee9
--- /dev/null
+++ b/src/behaviors/ExcelLogBehavior.php
@@ -0,0 +1,89 @@
+ 'afterExport',
+ ExcelAbstract::ENVENT_BEFORE_EXPORT => 'beforeExport',
+ ExcelAbstract::ENVENT_AFTER_IMPORT => 'afterImport',
+ ExcelAbstract::ENVENT_BEFORE_IMPORT => 'beforeImport',
+ ExcelAbstract::EVENT_AFTER_PROGRESS => 'afterProgress',
+ ExcelAbstract::EVENT_ERROR => 'error',
+ ];
+ }
+
+ /**
+ * 导出之前
+ *
+ * @param ExportEvent $event
+ * @return void
+ */
+ public function beforeExport(ExportEvent $event)
+ {
+
+ }
+
+ /**
+ * 导出之后
+ *
+ * @param ExportEvent $event
+ * @return void
+ * @throws ServiceException
+ */
+ public function afterExport(ExportEvent $event)
+ {
+
+ }
+
+ /**
+ * 导入之前
+ *
+ * @param ImportEvent $event
+ * @return void
+ */
+ public function beforeImport(ImportEvent $event)
+ {
+ }
+
+ /**
+ * 导入之后
+ *
+ * @param ImportEvent $event
+ * @return void
+ */
+ public function afterImport(ImportEvent $event)
+ {
+ }
+
+ /**
+ * 设置进度
+ *
+ * @param ProgressEvent $event
+ * @return void
+ */
+ public function afterProgress(ProgressEvent $event)
+ {
+
+ }
+
+ /**
+ * 异常
+ *
+ * @param ErrorEvent $event
+ * @return void
+ */
+ public function error(ErrorEvent $event)
+ {
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/data/export/Column.php b/src/data/export/Column.php
new file mode 100644
index 0000000..8cdbec7
--- /dev/null
+++ b/src/data/export/Column.php
@@ -0,0 +1,69 @@
+path;
+ }
+
+ /**
+ * 获取页码配置
+ *
+ * @return Sheet[]
+ */
+ public function getSheets()
+ {
+ return $this->sheets;
+ }
+
+ /**
+ * 设置页
+ *
+ * @param $sheets
+ * @return $this
+ */
+ public function setSheets($sheets)
+ {
+ $this->sheets = $sheets;
+ return $this;
+ }
+
+ /**
+ * 获取输出类型
+ *
+ * @return string
+ */
+ public function getOutPutType()
+ {
+ return $this->outPutType;
+ }
+
+ /**
+ * 获取文件名
+ *
+ * @return string
+ */
+ public function getFileName()
+ {
+ return basename($this->getPath());
+ }
+
+
+ /**
+ * 获取导出参数
+ *
+ * @return null
+ */
+ public function getParam()
+ {
+ return $this->param;
+ }
+
+ /**
+ * 是否异步
+ *
+ * @return bool
+ */
+ public function getIsAsync()
+ {
+ return $this->isAsync;
+ }
+
+ /**
+ * 获取token
+ *
+ * @return void
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+
+
+ /**
+ * 设置token
+ *
+ * @param $token
+ * @return $this
+ */
+ public function setToken($token)
+ {
+ $this->token = $token;
+ return $this;
+ }
+
+ /**
+ * 获取所有页名
+ *
+ * @return void
+ */
+ public function getSheetNames()
+ {
+ return array_map(function (Sheet $n) {
+ return $n->name;
+ }, $this->getSheets());
+ }
+
+
+ /**
+ * 序列化
+ *
+ * @return array
+ */
+ public function __serialize(): array
+ {
+ return [
+ 'token' => $this->getToken(),
+ 'path' => $this->getPath(),
+ 'isAsync' => $this->getIsAsync(),
+ 'outPutType' => $this->getOutPutType(),
+ 'param' => $this->getParam(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/src/data/export/ExportData.php b/src/data/export/ExportData.php
new file mode 100644
index 0000000..339837e
--- /dev/null
+++ b/src/data/export/ExportData.php
@@ -0,0 +1,52 @@
+path = $path;
+ return $this;
+ }
+
+
+ public function setToken($token)
+ {
+ $this->token = $token;
+ return $this;
+ }
+
+
+ public function setQueueId($queueId)
+ {
+ $this->queueId = $queueId;
+ return $this;
+ }
+
+}
\ No newline at end of file
diff --git a/src/data/export/Sheet.php b/src/data/export/Sheet.php
new file mode 100644
index 0000000..216861b
--- /dev/null
+++ b/src/data/export/Sheet.php
@@ -0,0 +1,150 @@
+columns;
+ }
+
+ /**
+ * 获取页码名
+ *
+ * @return void
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+
+ /**
+ * 获取数据
+ *
+ * @return void
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * 获取数据数量
+ *
+ * @return int
+ */
+ public function getCount()
+ {
+ return $this->count;
+ }
+
+ /**
+ * 获取每页导出数量
+ *
+ * @return int
+ */
+ public function getPageCount()
+ {
+ return $this->pageCount;
+
+ }
+
+ /**
+ * 获取头部信息
+ *
+ * @return array|string[]
+ */
+ public function getHeaders()
+ {
+ return array_map(function (Column $col) {
+ return $col->title;
+ }, $this->getColumns());
+ }
+
+
+ /**
+ * 格式行数据
+ *
+ * @param $row
+ * @return array
+ */
+ public function formatRow($row)
+ {
+ $newRow = [];
+ foreach ($this->columns as $column) {
+ $newRow[$column->field] = $row[$column->field] ?? '';
+ if (is_callable($column->callback)) {
+ $newRow[$column->field] = call_user_func($column->callback, $row);
+ }
+ }
+ return $newRow;
+ }
+
+ /**
+ * 格式化多行数据
+ *
+ * @param $list
+ * @return array
+ */
+ public function formatList($list)
+ {
+ return array_map([$this, 'formatRow'], $list ?? []);
+ }
+}
\ No newline at end of file
diff --git a/src/data/import/ImportConfig.php b/src/data/import/ImportConfig.php
new file mode 100644
index 0000000..ee1b391
--- /dev/null
+++ b/src/data/import/ImportConfig.php
@@ -0,0 +1,190 @@
+sheets;
+ }
+
+ /**
+ * 获取地址
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+
+ /**
+ * 获取token
+ *
+ * @return void
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+
+
+ /**
+ * 是否异步
+ *
+ * @return bool
+ */
+ public function getIsAsync(): bool
+ {
+ return $this->isAsync;
+ }
+
+ /**
+ * 设置页码信息
+ *
+ * @param array $sheets
+ * @return $this
+ */
+ public function setSheets(array $sheets)
+ {
+ $this->sheets = $sheets;
+ return $this;
+ }
+
+
+ /**
+ * 添加读取页
+ *
+ * @param Sheet $sheet
+ * @return void
+ */
+ public function addSheet(Sheet $sheet)
+ {
+ $this->sheets[] = $sheet;
+ return $this;
+ }
+
+ /**
+ * 设置导入地址
+ *
+ * @param string $path
+ * @return $this
+ */
+ public function setPath(string $path)
+ {
+ $this->path = $path;
+ return $this;
+ }
+
+ /**
+ * 设置token
+ *
+ * @return void
+ */
+ public function setToken($token)
+ {
+ $this->token = $token;
+ return $this;
+ }
+
+ /**
+ * 设置临时文件地址
+ *
+ * @param string $tempPath
+ * @return $this
+ */
+ final public function setTempPath(string $tempPath)
+ {
+ $this->tempPath = $tempPath;
+ return $this;
+ }
+
+ /**
+ * 获取临时文件地址
+ *
+ * @return string
+ */
+ final public function getTempPath(): string
+ {
+ return $this->tempPath;
+ }
+
+
+ /**
+ * 序列化
+ *
+ * @return array
+ */
+ public function __serialize(): array
+ {
+ return [
+ 'path' => $this->getPath(),
+ 'isAsync' => $this->isAsync,
+ 'token' => $this->getToken(),
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/src/data/import/ImportData.php b/src/data/import/ImportData.php
new file mode 100644
index 0000000..2c27b8e
--- /dev/null
+++ b/src/data/import/ImportData.php
@@ -0,0 +1,60 @@
+sheetDatas[strtolower($sheetName)] = $sheetData;
+ return $this;
+ }
+
+ /**
+ * 获取页数据
+ *
+ * @param $sheetName
+ * @return mixed
+ */
+ public function getSheetData($sheetName = 'sheet1')
+ {
+ return $this->getSheetData(strtolower($sheetName));
+ }
+
+}
\ No newline at end of file
diff --git a/src/data/import/Sheet.php b/src/data/import/Sheet.php
new file mode 100644
index 0000000..e225f43
--- /dev/null
+++ b/src/data/import/Sheet.php
@@ -0,0 +1,145 @@
+类型)
+ *
+ * @var array
+ */
+ public array $columnTypes = [];
+
+
+ /**
+ * 游标读取回调
+ * `
+ * function($row){
+ * // 执行业务代码
+ * }
+ * `
+ * @var
+ */
+ public $callback;
+
+
+ /**
+ * 获取最终列头信息
+ *
+ * @return void
+ */
+ public function getHeader(array $cols)
+ {
+ return array_map(function ($n) {
+ return $this->headerMap[$n] ?? $n;
+ }, $cols);
+ }
+
+ /**
+ * 格式化数据
+ *
+ * @param $sheetData
+ * @param $header
+ * @return void
+ */
+ public function formatSheetDataByHeader($sheetData, $header)
+ {
+ return array_map(function ($n) use ($header) {
+ return $this->formatRowByHeader($n, $header);
+ }, $sheetData);
+ }
+
+ /**
+ * 格式化行数据
+ *
+ * @param $row
+ * @param $header
+ * @return array
+ */
+ public function formatRowByHeader($row, $header)
+ {
+ return array_combine($header, $row);
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/drivers/csv/Excel.php b/src/drivers/csv/Excel.php
new file mode 100644
index 0000000..c339598
--- /dev/null
+++ b/src/drivers/csv/Excel.php
@@ -0,0 +1,29 @@
+excel = new \Vtiful\Kernel\Excel([
+ 'path' => $this->getTempDir(),
+ ]);
+ parent::init();
+ }
+
+
+ /**
+ * 导出
+ *
+ * @param ExportConfig $config
+ * @return void
+ */
+ protected function exportExcel(ExportConfig $config): string
+ {
+ $token = $config->getToken();
+
+ $filePath = $this->getTempFileName('ex_');
+ $fileName = basename($filePath);
+ $this->excel->fileName($fileName, ($config->sheets[0])->name ?? 'sheet1');
+
+ $this->setProgressInfo($token, $config->getSheetNames(), self::PROGRESS_STATUS_PROCESS);
+ /**
+ * 写入页码数据
+ *
+ * @var \vartruexuan\excel\data\export\Sheet $sheet
+ */
+ foreach ($config->getSheets() as $key => $sheet) {
+ if ($key > 0) {
+ $this->excel->addSheet($sheet->getName());
+ }
+ $this->excel->header($sheet->getHeaders());
+ $count = $sheet->getCount();
+ $pageCount = $sheet->getPageCount();
+ $data = $sheet->getData();
+ $this->initSheetProgress($token, $sheet->getName(), $sheet->getCount());
+ if (is_callable($data)) {
+ $pageNum = ceil($count / $pageCount);
+ $page = 1;
+ do {
+ $list = $this->exportDataFunc($data, $config, $page, $pageCount, $count, $sheet->getName());
+ $listCount = count($list ?? []);
+ if ($list) {
+ $this->excel->data($sheet->formatList($list));
+ }
+ $progressStatus = ($count <= 0 || ($listCount < $pageCount || $pageNum <= $page)) ? self::PROGRESS_STATUS_END : self::PROGRESS_STATUS_PROCESS;
+ $this->setSheetProgress($token, $sheet->getName(), $progressStatus, $listCount, $listCount);
+ $page++;
+ } while ($count > 0 && $listCount >= $pageCount && ($pageNum >= $page));
+ } else {
+ $this->excel->data($sheet->formatList($data));
+ $listCount = count($data ?? []);
+ $this->setSheetProgress($token, $sheet->getName(), self::PROGRESS_STATUS_END, $listCount, $listCount);
+ }
+ }
+
+ // 输出对应文件
+ $this->excel->output();
+
+ return $filePath;
+ }
+
+ /**
+ * 导入
+ *
+ * @param ImportConfig $config
+ * @return ImportData
+ */
+ protected function importExcel(ImportConfig $config): ImportData
+ {
+ $token = $config->getToken();
+ $importData = new ImportData([
+ 'token' => $token,
+ ]);
+ $filePath = $config->getTempPath();
+ $fileName = basename($filePath);
+ $this->checkFile($filePath);
+ // 打开文件
+ $this->excel->openFile($fileName);
+ $sheetList = $this->excel->sheetList();
+
+ // 初始化进度信息
+ $this->setProgressInfo($token, array_map('strtolower', $sheetList), self::PROGRESS_STATUS_PROCESS);
+ /**
+ * 页配置
+ *
+ * @var Sheet $sheetConfig
+ */
+ foreach ($config->getSheets() as $sheetConfig) {
+ $sheetName = $sheetConfig->name;
+ if ($sheetConfig->readType == Sheet::SHEET_READ_TYPE_INDEX) {
+ $sheetName = $sheetList[$sheetConfig->index];
+ }
+
+ $this->excel->openSheet($sheetName);
+
+ $header = [];
+ if ($sheetConfig->isSetHeader) {
+ // 跳过指定行
+ $header = $this->excel->nextRow();
+ $header = $sheetConfig->getHeader($header);
+ }
+ // 返回全量数据
+ if ($sheetConfig->isReturnSheetData) {
+ $sheetData = $this->excel->getSheetData();
+ $sheetDataCount = count($sheetData ?? []);
+ }
+ $this->initSheetProgress($token, $sheetName, $sheetDataCount ?? 0);
+ if ($sheetConfig->callback || $header) {
+ if ($sheetConfig->isReturnSheetData) {
+ foreach ($sheetData as $key => &$row) {
+ if ($header) {
+ $row = $sheetConfig->formatRowByHeader($row, $header);
+ }
+ // 执行回调
+ if ($sheetConfig->callback && is_callable($sheetConfig->callback)) {
+ $this->importRowCallback($sheetConfig->callback, $row, $this, $config, $sheetName);
+ }
+ }
+ $importData->addSheetData($sheetData, $sheetName);
+ } else {
+ // 执行回调
+ if ($sheetConfig->callback) {
+ while (null !== $row = $this->excel->nextRow()) {
+ $this->importRowCallback($sheetConfig->callback, $sheetConfig->formatRowByHeader($row, $header), $this, $sheetConfig, $sheetName);
+ }
+ }
+ }
+ }
+ $this->setSheetProgress($token, $sheetName, self::PROGRESS_STATUS_END);
+ }
+
+ // 删除临时文件
+ @$this->deleteFile($filePath);
+
+ $this->excel->close();
+ return $importData;
+ }
+
+
+ /**
+ * 校验文件mimeType类型
+ *
+ * @param $filePath
+ * @return void
+ */
+ protected function checkFile($filePath)
+ {
+ // 本地地址
+ if (!file_exists($filePath)) {
+ throw new ExcelException('File does not exist');
+ }
+ // 校验mime type
+ $mimeType = FileHelper::getMimeType($filePath);
+ if (!in_array($mimeType, [
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'application/vnd.ms-excel',
+ 'application/octet-stream',
+ ])) {
+ throw new ExcelException('File mime type error');
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/events/ErrorEvent.php b/src/events/ErrorEvent.php
new file mode 100644
index 0000000..5c864c6
--- /dev/null
+++ b/src/events/ErrorEvent.php
@@ -0,0 +1,29 @@
+exportConfig->isAsync = false;
+ $this->getExcelInstance()->export($this->exportConfig);
+ } catch (\Throwable $exception) {
+ \Yii::error($exception->getMessage(), 'vartruexuan/excel/ExportJob');
+ throw $exception;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jobs/ImportJob.php b/src/jobs/ImportJob.php
new file mode 100644
index 0000000..8f3a18d
--- /dev/null
+++ b/src/jobs/ImportJob.php
@@ -0,0 +1,36 @@
+importConfig->isAsync = false;
+ $this->getExcelInstance()->import($this->importConfig);
+ } catch (\Throwable $exception) {
+ \Yii::error($exception->getMessage(), 'vartruexuan/excel/ImportJob');
+ throw $exception;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/jobs/base/BaseJob.php b/src/jobs/base/BaseJob.php
new file mode 100644
index 0000000..3c564e4
--- /dev/null
+++ b/src/jobs/base/BaseJob.php
@@ -0,0 +1,28 @@
+getExcelInstance($this->componentId);
+ }
+
+}
\ No newline at end of file
diff --git a/src/utils/Helper.php b/src/utils/Helper.php
new file mode 100644
index 0000000..99a4d97
--- /dev/null
+++ b/src/utils/Helper.php
@@ -0,0 +1,72 @@
+ 'raw',
+ ])->request($remotePath, 'GET', [
+ 'verify' => false,
+ 'http_errors' => false,
+ ]);
+
+ if (@file_put_contents($filePath, $response->getBody()->getContents())) {
+ return $filePath;
+ }
+ return false;
+ }
+
+
+ /**
+ * 是否是远程地址
+ *
+ * @param $url
+ * @return false|int
+ */
+ public function isUrl($url)
+ {
+ return preg_match('/^http[s]?:\/\//', $url);
+ }
+
+
+ /**
+ * 获取uuid4
+ *
+ * @return void
+ */
+ public function uuid4()
+ {
+ return Uuid::uuid4()->getHex()->toString();
+ }
+
+ /**
+ * 删除文件
+ *
+ * @param string $filePath
+ * @return bool
+ */
+ public function deleteFile(string $filePath)
+ {
+ return FileHelper::unlink($filepath);
+ }
+
+}
\ No newline at end of file
diff --git a/tests/ExportTest.php b/tests/ExportTest.php
new file mode 100644
index 0000000..7fe8c2b
--- /dev/null
+++ b/tests/ExportTest.php
@@ -0,0 +1,79 @@
+export(new ExportConfig([
+ 'path' => '/www/export_'.Carbon::now()->format('Y_m_d_H_i_s_u').'.xlsx',
+ 'outPutType' => ExportConfig::OUT_PUT_TYPE_LOCAL,
+ 'sheets' => [
+ new \vartruexuan\excel\data\export\Sheet([
+ 'name' => 'sheet1',
+ 'columns' => [
+ new Column([
+ 'title' => 'uniqId',
+ 'key' => 'uniqId',
+ 'field' => 'uniqId',
+ ]),
+ new Column([
+ 'title' => '标题',
+ 'key' => 'title',
+ 'field' => 'title',
+ ]),
+ new Column([
+ 'title' => 'component',
+ 'key' => 'component',
+ 'field' => 'component',
+ ]),
+ ],
+ 'count' => PlatformMenuService::instance()->getCount(),
+ 'data' => function ($page, $pageCount, $total) {
+ $pageInfo = Helper::getPageInfo($total, $pageCount, $page);
+ return PlatformMenuService::instance()->getList(null,null,$pageInfo['limit'],$pageInfo['offset'],true);
+ },
+ ]),
+ new \vartruexuan\excel\data\export\Sheet([
+ 'name' => 'sheet2',
+ 'columns' => [
+ new Column([
+ 'title' => 'uniqId',
+ 'key' => 'uniqId',
+ 'field' => 'uniqId',
+ ]),
+ new Column([
+ 'title' => '标题',
+ 'key' => 'title',
+ 'field' => 'title',
+ ]),
+ new Column([
+ 'title' => 'component',
+ 'key' => 'component',
+ 'field' => 'component',
+ ]),
+ ],
+ 'count' => PlatformMenuService::instance()->getCount(),
+ 'data' => function ($page, $pageCount, $total) {
+ $pageInfo = Helper::getPageInfo($total, $pageCount, $page);
+ return PlatformMenuService::instance()->getList(null,null,$pageInfo['limit'],$pageInfo['offset'],true);
+ },
+ ])
+ ]
+ ]));
+
+ }
+
+}
\ No newline at end of file
diff --git a/tests/ImportTest.php b/tests/ImportTest.php
new file mode 100644
index 0000000..f339004
--- /dev/null
+++ b/tests/ImportTest.php
@@ -0,0 +1,41 @@
+import(new \vartruexuan\excel\data\import\ImportConfig([
+ 'path' => '../files/ceshi.xlsx',
+ // 'path' => 'https://pubbucket-1259747003.cos.ap-guangzhou.myqcloud.com/ceshi.xlsx',
+
+ 'sheets' => [
+ new Sheet([
+ 'name' => 'sheet1',
+ 'isReturnSheetData' => false,
+ 'isSetHeader' => true,
+ 'headerMap' => [
+ '年龄' => 'age',
+ '姓名' => 'name',
+ '身高' => 'height',
+ ],
+ 'callback' => function ($row) {
+ // 执行行回调
+ var_dump($row);
+ }
+ ]),
+ ],
+ ]));
+
+ }
+
+}
\ No newline at end of file
diff --git a/tests/config/TestExportConfig.php b/tests/config/TestExportConfig.php
new file mode 100644
index 0000000..fa1cd18
--- /dev/null
+++ b/tests/config/TestExportConfig.php
@@ -0,0 +1,99 @@
+path;
+ }
+
+ /**
+ * 获取页码配置
+ *
+ * @return Sheet[]
+ */
+ public function getSheets()
+ {
+ return [
+ new \vartruexuan\excel\data\export\Sheet([
+ 'name' => 'sheet1',
+ 'pageCount' => 5000,
+ 'columns' => [
+ new Column([
+ 'title' => 'uniqId',
+ 'key' => 'uniqId',
+ 'field' => 'uniqId',
+ ]),
+ new Column([
+ 'title' => '标题',
+ 'key' => 'title',
+ 'field' => 'title',
+ ]),
+ new Column([
+ 'title' => 'component',
+ 'key' => 'component',
+ 'field' => 'component',
+ ]),
+ ],
+ 'count' => PlatformMenuService::instance()->getCount(),
+ 'data' => function (ExportConfig $config, $page, $pageSize, $total, $sheetName, ExcelAbstract $excel) {
+ $pageInfo = Helper::getPageInfo($total, $pageCount, $page, null, [1, $pageCount]);
+ return PlatformMenuService::instance()->getList(null, null, $pageInfo['limit'], $pageInfo['offset'], true);
+ },
+ ]),
+ new \vartruexuan\excel\data\export\Sheet([
+ 'name' => 'sheet2',
+ 'pageCount' => 5000,
+ 'columns' => [
+ new Column([
+ 'title' => 'uniqId',
+ 'key' => 'uniqId',
+ 'field' => 'uniqId',
+ ]),
+ new Column([
+ 'title' => '标题',
+ 'key' => 'title',
+ 'field' => 'title',
+ ]),
+ new Column([
+ 'title' => 'component',
+ 'key' => 'component',
+ 'field' => 'component',
+ ]),
+ ],
+ 'count' => PlatformMenuService::instance()->getCount(),
+ 'data' => function ($page, $pageCount, $total) {
+ var_dump($page, $pageCount, $total);
+ $pageInfo = Helper::getPageInfo($total, $pageCount, $page, null, [1, $pageCount]);
+ return PlatformMenuService::instance()->getList(null, null, $pageInfo['limit'], $pageInfo['offset'], true);
+ },
+ ])
+ ];
+ }
+
+ /**
+ * 获取输出类型
+ *
+ * @return string
+ */
+ public function getOutPutType()
+ {
+ return $this->outPutType;
+ }
+
+
+}
\ No newline at end of file
diff --git a/tests/config/TestImportConfig.php b/tests/config/TestImportConfig.php
new file mode 100644
index 0000000..a016537
--- /dev/null
+++ b/tests/config/TestImportConfig.php
@@ -0,0 +1,38 @@
+setSheets([
+ new Sheet([
+ 'name' => 'sheet1',
+ 'isReturnSheetData' => true,
+ 'isSetHeader' => true,
+ 'headerMap' => [
+ '年龄' => 'age',
+ '姓名' => 'name',
+ '身高' => 'height',
+ ],
+ 'callback' => function ($row, ExcelAbstract $excel, ImportConfig $config) {
+ // 执行行回调
+ var_dump($row);
+
+ sleep(10);
+
+ return true;
+ }
+ ])
+
+ ]);
+ return parent::getSheets();
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/tests/files/ceshi.xlsx b/tests/files/ceshi.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..6aa4a4b8d8e8233365ea60007d4b8f165a3275ec
GIT binary patch
literal 8697
zcmeHsgFt|
zFz^_(+~15_V*E~I5Z<+-@%S@Hc(`~P_+^rC#?gTdYJU2KOBpSe9=b^4V_v95XQ<0&
zrPJ(jm8$yQleOu=>=SF0&%BX|?f6S$%NVWig{aj9C~AtIpOqN*Cqg(n0!#y*cCfyP
zJ=3~s5hS*^;4;aP3Z)C1ng?h>V(UoC`CY~XdnJhr)JBfFX_u~