$taskId ] ); } /** * */ public function init() { parent::init(); $this->isRemote = \Yii::$app->params['isRemoteUpload']; } /** * 生成复杂的路径 * @return string */ public function pathCreator(...$params) { $hash = md5(implode('_', $params)); $first = substr($hash, 0, 3); $second = substr($hash, 3, 2); $dir = $first . __DS__ . $second . __DS__; return $dir; } /** * 获取文件名 * @return string|null * @throws \Exception */ public function getFileName() { $this->fileName = date('YmdHis', Date::nowTime()) . random_int(1000, 9999) . '.csv'; return $this->fileName; } /** * 获取保存的路径 * @return array|null */ public function getSavePath() { $this->savePath = $this->pathCreator(date('Ymd', Date::nowTime())); return rtrim($this->savePath, __DS__); } /** * @return bool|string|null */ public function getSaveBasePath() { $this->saveBasePath = Yii::getAlias('@backendApi') . __DS__ . 'web' . __DS__ . 'upload' . __DS__ . \Yii::$app->params['excelLocalDir']; return rtrim($this->saveBasePath, __DS__); } /** * 创建目录(递归创建) * * @param $dir * @return string|false */ public function mkdir($dir) { if (!is_dir($dir)) { $this->mkdir(dirname($dir)); if (!mkdir($dir, 0777)) return false; @exec('chown -R www:www /'.$dir); @exec('chmod -R 777 /'.$dir); } return $dir; } /** * @return array|mixed */ public function getParams() { $this->params = CacheHelper::getAsyncParams($this->taskId); return $this->params; } /** * @return mixed|null */ public function getUserId() { $this->userId = isset($this->params['userId']) ? $this->params['userId'] : null; return $this->userId; } /** * @return |null */ public function getExportId() { $this->exportId = isset($this->params['exportId']) ? $this->params['exportId'] : null; return $this->userId; } /** * 生成 * @return bool * @throws Exception * @throws \yii\base\InvalidConfigException * @throws \yii\httpclient\Exception */ public function generate() { //Logger::info(date('Y-m-d H:i:s'), 'export'); $this->getParams(); if (!$this->params) { throw new Exception('无法获取需要的参数'); } $path = $this->getSaveBasePath() . __DS__ . $this->getSavePath(); $realFile = $this->mkdir($path) . __DS__ . $this->getFileName(); $this->completed = false; $this->getExportId(); $this->getUserId(); $fileNameUpdated = false; $this->_fp = fopen($realFile, 'w'); @exec('chown -R www:www /'.$realFile); @exec('chmod -R 777 /'.$realFile); // 获取列表数据及表头 $this->_listModel = new $this->listModelClass(); $this->_listModel->isExport = true; if(method_exists($this->_listModel, 'getExportHeaders')){ if(method_exists($this->_listModel, 'exportPrepare')) {//导出数据提前设置参数 $this->_listModel->exportPrepare(['condition' => $this->params['condition'], 'params' => $this->params['params'], 'others' => isset($this->params['others']) ? $this->params['others'] : [], 'page' => 0, 'pageSize' => $this->pageSize, 'userId' => $this->userId]); } $headers = $this->_listModel->getExportHeaders($this->userId); fputcsv($this->_fp, $headers); unset($headers); $this->_updateFirst($realFile, 1); } else { throw new Exception($this->listModelClass.'的getExportHeaders方法不存在'); } $this->_loopWriteData($realFile, $fileNameUpdated); $this->complete(); return true; } /** * 循环写入数据 * @param $realFile * @param $fileNameUpdated * @param int $page * @param int $counter * @return string * @throws Exception */ private function _loopWriteData($realFile, &$fileNameUpdated, $page = 0, $counter = 0){ if(method_exists($this->_listModel, 'getList')){ $list = $this->_listModel->getList(['condition'=>$this->params['condition'], 'params'=>isset($this->params['params']) ? $this->params['params'] : [], 'others'=>isset($this->params['others']) ? $this->params['others'] : [], 'page'=>$page, 'pageSize'=>$this->pageSize, 'userId'=>$this->userId]); } else { throw new Exception($this->listModelClass.'的getList方法不存在'); } if($page >= $list['totalPages']){ return 'finish'; } if(!empty($list['list'])){ foreach($list['list'] as $columnData){ fputcsv($this->_fp, Tool::arrTextConvert($columnData)); $counter++; if ($list['totalCount'] < $counter) { // $counter = $list['totalCount']; return 'finish'; } $percent = intval(($counter / $list['totalCount']) * 100); $this->_updatePercent($percent); // if (!$fileNameUpdated) { // $this->_updateFirst($realFile, $percent); // } else { // $this->_updatePercent($percent); // } // $fileNameUpdated = true; unset($percent, $columnData); } $page++; unset($list); return $this->_loopWriteData($realFile, $fileNameUpdated, $page, $counter); } unset($list); return 'finish'; } /** * 完成 * @return bool * @throws Exception * @throws \yii\base\InvalidConfigException * @throws \yii\httpclient\Exception */ public function complete() { $this->_updatePercent(100); if ($this->completed) { return true; } if ($this->_fp) { fclose($this->_fp); } // 把导出的文件上传至静态文件服务器 if ($this->isRemote) { $realFile = $this->getSaveBasePath() . __DS__ . $this->getSavePath() . __DS__ . $this->getFileName(); $remoteUploadApi = RemoteUploadApi::instance(); if ($uploadResult = $remoteUploadApi->upload($realFile)) { Export::updateAll(['REMOTE_URL' => $uploadResult['url'], 'IS_EXPORTING' => 0, 'EXPORT_PERCENT' => 100, 'ENDED_AT' => Date::nowTime()], 'ID=:ID', [':ID' => $this->exportId]); } else { $this->_errorHandle(); throw new Exception('文件远程上传失败'); } // 删除本地临时文件 unlink($realFile); } else { Export::updateAll(['IS_EXPORTING' => 0, 'EXPORT_PERCENT' => 100, 'ENDED_AT' => Date::nowTime()], 'ID=:ID', [':ID' => $this->exportId]); } \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin(100, ['MODEL' => 'EXPORT', 'ID' => $this->exportId, 'FIELD' => 'EXPORT_PERCENT']); CacheHelper::deleteAsyncParams($this->taskId); $this->completed = true; } /** * @param $message */ private function _errorNotice($message) { Yii::$app->swooleAsyncTimer->pushAsyncResultToAdmin($this->userId, $message, false); } /** * 首次更新 * @param $exportId * @param $realName */ private function _updateFirst($realName, $percent) { $fileName = str_replace($this->getSaveBasePath() . __DS__, '', $realName); Export::updateAll([ 'FILE_NAME' => str_replace(__DS__, '/', $fileName), 'UPDATED_AT' => Date::nowTime(), 'EXPORT_PERCENT' => $percent, ], 'ID=:ID', [':ID' => $this->exportId]); } /** * 更新百分比 * @param $exportId * @param $percent */ private function _updatePercent($percent) { // 把数据写入数据库中 Export::updateAll(['EXPORT_PERCENT' => $percent], 'ID=:ID', [':ID' => $this->exportId]); \Yii::$app->swooleAsyncTimer->pushAsyncPercentToAdmin($percent, ['MODEL' => 'EXPORT', 'ID' => $this->exportId, 'FIELD' => 'EXPORT_PERCENT']); } /** * 发生错误处理 * @param $exportId */ private function _errorHandle() { Export::updateAll(['IS_EXPORTING' => 0], 'ID=:ID', [':ID' => $this->exportId]); } /** * 导出逻辑 * @param $filter * @param $listName * @param null $consoleRouter * @throws Exception */ public function exportHandle($filter, $listName, $consoleRouter = null){ $params = [ 'moduleId' => $this->moduleId, 'listName' => $listName, 'action' => $consoleRouter ? $consoleRouter : Yii::$app->controller->id.'/'.Yii::$app->controller->action->id, // 这里这么写,是因为调用的异步路由和同步的控制器和方法是一样的,所以,只要不传默认调和同步一样的异步方法 'userId' => Yii::$app->user->id, ]; $this->webToAsync($params,$filter); } /** * 页面到异步的请求 * @param $params * @param array $extend * @return bool * @throws Exception */ public function webToAsync($params, $extend = []) { $default = [ 'moduleId' => null, 'listName' => null, 'remark' => null, 'action' => null, 'userId' => null, ]; $params = Tool::deepParse($params, $default); if (!$params['moduleId'] || !$params['listName'] || !$params['action']) { throw new Exception('请设置moduleId,listName和action'); } // 把文件对应的相关资料存入数据库中 $export = new Export(); $export->EXPORT_NAME = $params['listName'] . '_' . date('y年m月d日H时i分s秒导出', Date::nowTime()); $export->MODULE_NAME = $params['moduleId']; $export->REMARK = $params['remark']; $export->IS_EXPORTING = 1; $export->ADMIN_ID = \Yii::$app->user->id; $export->STARTED_AT = Date::nowTime(); $export->CREATED_AT = Date::nowTime(); $export->EXPORT_PERCENT = 0; $export->ENDED_AT = 0; $export->UPDATED_AT = 0; if (!$export->save()) { throw new Exception(Form::formatErrorsForApi($export->getErrors())); } $extend = array_merge($extend, [ 'exportId' => $export->ID, 'userId' => $params['userId'], ]); // 异步处理添加任务 $taskKey = \Yii::$app->swooleAsyncTimer->asyncHandle($params['action'], $extend); if($taskKey === false){ // 删除刚刚添加的导出数据 Export::deleteAll(['ID'=>$export->ID]); throw new Exception('导出失败,请求异步处理服务器失败'); } return $taskKey; } }