app = $app; } /** * @param UploadedFile $file 上传的文件 * @param int $directory_id 存储的目录位置 * @param int $user_id 用户ID * @param array $vars * @return array */ public function upload(UploadedFile $file, int $directory_id, int $user_id, array $vars = []): array { $rule_id = 1; if ($directory_id > 0) { // 上传到某个目录下 $directory = SysUserFile::where('is_directory', 1)->find($directory_id); if (empty($directory)) { throw new ValidateException('Dir: 目录不存在'); } } /* * 文件记录实体 */ $filesystem_model = $this->app->config->get('filesystem.model'); if (empty($filesystem_model)) { throw new FileException('File记录模型未定义'); } /** * @var SysFileRule $fileRule */ $fileRule = SysFileRule::find($rule_id); if (empty($fileRule)) { throw new ValidateException('Rule: 上传规则不存在'); } if ($fileRule['is_close']) { throw new ValidateException('Rule: 已关闭上传权限'); } /* * 根据规则校验上传的文件是否符合要求 */ $this->checkFileRule($fileRule, $file); $putDisk = $fileRule->disk; $putUrl = $fileRule->url; $putPath = $fileRule->path_rule; $nameRule = $fileRule->name_rule; $nameRule = $this->replaceName($nameRule, $file, $vars + ['uid' => $user_id]); $putPath = $this->replaceName($putPath, $file, $vars + ['uid' => $user_id]); $savePath = ($putPath ? trim($putPath, '/') : '') . '/' . $nameRule; $fileName = pathinfo($savePath, PATHINFO_FILENAME); $filePath = pathinfo($savePath, PATHINFO_DIRNAME); // dump(['#1', $putPath, $nameRule]); // dump(['#2', $filePath, $fileName]); $filesystemSrv = Filesystem::disk($putDisk); $uploadPath = $filesystemSrv->putFile($filePath, $file, function () use ($fileName) { return $fileName; }); if ($uploadPath === false) { throw new FileException('上传失败'); } $diskConfig = Filesystem::getDiskConfig($putDisk); $url = $diskConfig['url'] ?? ''; if ($url && Validate::url($url) === false) { $url = trim($url, '/'); $uploadPath = "$url/$uploadPath"; $url = ""; } $filesystem_apiUrl = $this->app->config->get('filesystem.api_url'); /* * URL取值优先级为 * #1 取fileRule中的设置 -> #2 取config中的设置 -> #3 取api_url的配置 #4 取当前域名的配置 * 注意, fileRule中的permissions配置会把#3,#4做为优先项 */ $diskUrl = $putUrl ?: ($url ?: $filesystem_apiUrl ?: Request::domain()); $uploadSavePath = pathinfo($uploadPath, PATHINFO_DIRNAME); $fileSaveName = pathinfo($uploadPath, PATHINFO_BASENAME); // dump([$diskUrl, $uploadPath]); // dump(['#4', $uploadSavePath, $fileSaveName]); $sysFileRecord = $this->app->invokeClass($filesystem_model); $sysFileRecord->save([ 'name' => $fileSaveName, 'origin_name' => $file->getOriginalName(), 'alias_name' => '', 'path' => $uploadSavePath, 'length' => $file->getSize(), 'extension' => $file->getOriginalExtension(), 'content_type' => mime_content_type($file->getRealPath()) ?: '', 'md5' => $file->md5(), 'sha1' => $file->sha1(), 'disk' => $putDisk, 'uid' => $user_id, 'create_user_id' => $user_id, 'context_id' => $this->app->request->contextId, 'gid' => 0, 'create_date' => date('Y-m-d'), 'create_time' => date('Y-m-d H:i:s'), 'update_time' => date('Y-m-d H:i:s'), 'delete_time' => null, 'uuid' => guid() ]); $sysFileRecordData = $sysFileRecord->toArray(); $fileRuleData = $fileRule->toArray(); if ($user_id > 0) { (new SysUserFile)->save([ 'user_id' => $user_id, 'name' => $file->getOriginalName(), 'is_directory' => 0, 'parent_id' => $directory_id, 'path' => $uploadPath, 'length' => $file->getSize(), 'content_type' => $file->getOriginalMime(), 'deleted' => 0, // 磁盘 'disk' => $putDisk, // 存储规则 'rule_id' => $rule_id, 'uuid' => $sysFileRecord->uuid, 'url' => "$diskUrl/{$uploadPath}", 'is_anonymous' => 0, ]); } // 是否需要安全访问 if ($fileRule->permissions) { $uploadPath = "i/" . $sysFileRecord->uuid; $diskUrl = $filesystem_apiUrl ?: Request::domain(); } $results = [ // 文件对外显示URL 'url' => "$diskUrl/{$uploadPath}", // 文件对外显示存储路径 'path' => $uploadPath, // 文件唯一标识符 'uuid' => "{$rule_id}@" . $sysFileRecord->uuid, // 文件上传时间戳 'time' => time(), ]; // 触发文件上传事件 Event::trigger('upload', ['results' => $results, 'file' => $file, 'rule' => $fileRuleData, 'model' => $sysFileRecordData, 'directory_id' => $directory_id]); // 触发磁盘上传事件 Event::trigger("upload.$putDisk", ['results' => $results, 'file' => $file, 'rule' => $fileRuleData, 'model' => $sysFileRecordData, 'directory_id' => $directory_id]); // 触发磁盘某个规则的上传事件 Event::trigger("upload.rule_$rule_id", ['results' => $results, 'file' => $file, 'rule' => $fileRuleData, 'model' => $sysFileRecordData, 'directory_id' => $directory_id]); return $results; } private function replaceName(string $pathname, UploadedFile $file, $vars = []): string { $array = [ '{Y}' => date('Y'), '{y}' => date('y'), '{m}' => date('m'), '{d}' => date('d'), '{H}' => date('H'), '{i}' => date('i'), '{s}' => date('s'), '{G}' => date('G'), '{Ym}' => date('Ym'), '{Ymd}' => date('Ymd'), '{YmdH}' => date('YmdH'), '{timestamp}' => time(), '{uniqid}' => uniqid(), '{md5}' => md5(microtime() . Str::random()), '{md5-16}' => substr(md5(microtime() . Str::random()), 0, 16), '{str-random-16}' => Str::random(16), '{str-random-10}' => Str::random(10), '{str-random-6}' => Str::random(6), '{fmd5}' => $file->md5(), '{fsha1}' => $file->sha1(), '{fname}' => $file->getOriginalName(), '{uid}' => $vars['uid'] ?? 'UID', ]; // 系统内部的key不允许被覆盖 $useKeys = array_keys($array); foreach ($vars as $key => $value) { if (in_array($key, $useKeys)) { continue; } $array["{{$key}}"] = $value; } return str_replace(array_keys($array), array_values($array), $pathname); } private function checkFileRule(SysFileRule|\app\model\SysFileRule $fileRule, UploadedFile $file): void { /* * 验证文件信息 */ if ($fileRule->rule_file_size_limit_min > 0) { if ($file->getSize() < $fileRule->rule_file_size_limit_min * 1024) { throw new ValidateException('上传限制:文件太小了'); } } $validateRule = []; if ($fileRule->rule_file_size_limit_max > 0) { $validateRule[] = 'fileSize:' . ($fileRule->rule_file_size_limit_max * 1024); } if ($fileRule->rule_file_ext !== '') { $validateRule[] = "fileExt:$fileRule->rule_file_ext"; } if ($fileRule->rule_file_mime !== '') { $validateRule[] = "fileMime:$fileRule->rule_file_mime"; } if ($fileRule->rule_image !== '') { $validateRule[] = "image:$fileRule->rule_image"; } if ($validateRule) { validate(['file' => implode('|', $validateRule)]) ->check(['file' => $file]); } } }