diff --git a/app/Constants/ErrorCode.php b/app/Constants/ErrorCode.php index a7fee16..1a1dc07 100644 --- a/app/Constants/ErrorCode.php +++ b/app/Constants/ErrorCode.php @@ -25,12 +25,17 @@ class ErrorCode extends AbstractConstants const SERVER_ERROR = 500; /** - * @Message("Params Invalid!") + * @Message("Params Invalid!") */ const PARAMS_INVALID = 900; /** - * @Message("Save failure!"); + * @Message("Save Failure!") */ const SAVE_FAILURE = 100; + + /** + * @Message("文件上传异常") + */ + const UPLOAD_INVALID = 200; } diff --git a/app/Controller/AttachmentController.php b/app/Controller/AttachmentController.php new file mode 100644 index 0000000..efbf45d --- /dev/null +++ b/app/Controller/AttachmentController.php @@ -0,0 +1,68 @@ +request->file('upload'); + $type = $this->request->input('type', ''); + + $fileName = $this->attachmentService->formUpload($file, $type, $filesystem, 'file'); + + return $this->success(['file_path' => $fileName]); + } + + /** + * 单图表单上传 + * @param ImageRequest $request + * @param Filesystem $filesystem + * @return \Psr\Http\Message\ResponseInterface + */ + public function uploadImage(ImageRequest $request, Filesystem $filesystem) + { + $file = $this->request->file('upload'); + $type = $this->request->input('type', ''); + + $fileName = $this->attachmentService->formUpload($file, $type, $filesystem); + + return $this->success(['file_path' => $fileName]); + } + + /** + * 单图base64上传 + * @param ImageBase64Request $request + * @param Filesystem $filesystem + * @return \Psr\Http\Message\ResponseInterface + */ + public function uploadImageByBase64(ImageBase64Request $request, Filesystem $filesystem) + { + $base64Code = $this->request->input('upload'); + $type = $this->request->input('type', ''); + + $fileName = $this->attachmentService->base64Upload($base64Code, $type, $filesystem); + + return $this->success(['file_path' => $fileName]); + } + +} \ No newline at end of file diff --git a/app/Exception/Handler/FilesystemExceptionHandler.php b/app/Exception/Handler/FilesystemExceptionHandler.php new file mode 100644 index 0000000..f5f1b1c --- /dev/null +++ b/app/Exception/Handler/FilesystemExceptionHandler.php @@ -0,0 +1,35 @@ +stopPropagation(); + + $content = json_encode([ + "status" => 'error', + "code" => ErrorCode::UPLOAD_INVALID, + "result" => [], + "message" => $throwable->getMessage() + ]); + + return $response->withHeader('Content-Type', 'application/json') + ->withStatus($throwable->status) + ->withBody(new SwooleStream($content)); + } + + public function isValid(Throwable $throwable): bool + { + return $throwable instanceof FilesystemException; + } +} \ No newline at end of file diff --git a/app/Listener/ValidatorFactoryResolvedListener.php b/app/Listener/ValidatorFactoryResolvedListener.php index 0bfb047..763ebf3 100644 --- a/app/Listener/ValidatorFactoryResolvedListener.php +++ b/app/Listener/ValidatorFactoryResolvedListener.php @@ -80,5 +80,35 @@ class ValidatorFactoryResolvedListener implements ListenerInterface return $builder->exists(); }); + // 注册了 base64 验证器规则 + $validatorFactory->extend('base64', function ($attribute, $value, $parameters, $validator) { + + preg_match('/^(data:\s*image\/(\w+);base64,)/', $value, $result); + + if (empty($result)) { + return false; + } + + if ( + in_array('image', $parameters) + && !in_array($result[2], ['jpg','jpeg','png','gif','svg','bmp']) + ) { + return false; + } + + return true; + + }); + + // 注册了 ext_not_in 验证器规则 + $validatorFactory->extend('ext_not_in', function ($attribute, $value, $parameters, $validator) { + + if (empty($parameters)) { + $parameters = ['', 'php', 'exe', 'sql', 'sh', 'bat', 'py', 'go', 'c', 'cpp']; + } + return !in_array($value->getExtension(), $parameters); + + }); + } } \ No newline at end of file diff --git a/app/Request/AttachmentRequest.php b/app/Request/AttachmentRequest.php new file mode 100644 index 0000000..da2e1f0 --- /dev/null +++ b/app/Request/AttachmentRequest.php @@ -0,0 +1,47 @@ + 'required|nonempty|file|ext_not_in', + 'type' => 'nonempty|alpha' + ]; + } + + public function messages(): array + { + return [ + 'upload.required' => '未选择上传的文件', + 'upload.nonempty' => '文件异常', + 'upload.file' => '文件上传不成功', + 'upload.ext_not_in' => '文件不允许上传', + 'type.nonempty' => '文件类型参数异常', + ]; + } + + public function attributes(): array + { + return [ + 'upload' => '文件' + ]; + } +} diff --git a/app/Request/ImageBase64Request.php b/app/Request/ImageBase64Request.php new file mode 100644 index 0000000..88a752a --- /dev/null +++ b/app/Request/ImageBase64Request.php @@ -0,0 +1,46 @@ + 'required|nonempty|base64:image', + 'type' => 'nonempty|alpha' + ]; + } + + public function messages(): array + { + return [ + 'upload.required' => '未选择上传的文件', + 'upload.base64' => '文件不是正常的图片', + 'upload.nonempty' => '文件异常', + 'type.nonempty' => '图片类型参数异常', + ]; + } + + public function attributes(): array + { + return [ + 'upload' => '图片' + ]; + } +} diff --git a/app/Request/ImageRequest.php b/app/Request/ImageRequest.php new file mode 100644 index 0000000..85bad47 --- /dev/null +++ b/app/Request/ImageRequest.php @@ -0,0 +1,46 @@ + 'required|nonempty|file|image', + 'type' => 'nonempty|alpha' + ]; + } + + public function messages(): array + { + return [ + 'upload.required' => '未选择上传的文件', + 'upload.image' => '文件不是正常的图片', + 'upload.nonempty' => '文件异常', + 'type.nonempty' => '图片类型参数异常', + ]; + } + + public function attributes(): array + { + return [ + 'upload' => '图片' + ]; + } +} diff --git a/app/Service/AttachmentService.php b/app/Service/AttachmentService.php new file mode 100644 index 0000000..65ab9a4 --- /dev/null +++ b/app/Service/AttachmentService.php @@ -0,0 +1,73 @@ +getRealPath(); + $fileHash = md5_file($fileRealPath); + + $path = $this->getBasePath($path, $attachmenttype); + $fileName = $path . '/' . $fileHash . '.' . $file->getExtension(); + + $stream = fopen($fileRealPath, 'r+'); + $filesystem->writeStream($fileName, $stream); + fclose($stream); + + return $fileName; + } + + /** + * @inheritDoc + */ + public function base64Upload($contents, $path, $filesystem) + { + + preg_match('/^(data:\s*image\/(\w+);base64,)/', $contents, $result); + if (empty($result)) { + throw new FilesystemNotFoundException(ErrorCode::getMessage(ErrorCode::UPLOAD_INVALID),ErrorCode::UPLOAD_INVALID); + } + + $contents = base64_decode(str_replace($result[1], '', $contents)); + + $fileHash = md5($contents); + $path = $this->getBasePath($path); + $fileName = $path . '/' . $fileHash . '.' . $result[2]; + + $filesystem->write($fileName, $contents); + + return $fileName; + } + + protected function getBasePath($path, $attachmenttype = 'image') + { + switch ($attachmenttype) { + case 'image': + $baseDir = env('IMAGE_BASE', '/attachment/images'); + break; + + case 'file': + $baseDir = env('FILES_BASE', '/attachment/files'); + break; + + default: + $baseDir = env('FILES_BASE', '/attachment'); + break; + } + + $path = $path ? '/'.$path : ''; + $path .= '/'.date('Y').'/'.date('m').'/'.date('d'); + return $baseDir.$path; + } +} \ No newline at end of file diff --git a/app/Service/AttachmentServiceInterface.php b/app/Service/AttachmentServiceInterface.php new file mode 100644 index 0000000..28439b2 --- /dev/null +++ b/app/Service/AttachmentServiceInterface.php @@ -0,0 +1,23 @@ + 'oss', + 'storage' => [ + 'local' => [ + 'driver' => \Hyperf\Filesystem\Adapter\LocalAdapterFactory::class, + 'root' => BASE_PATH . '/attachment', + ], + 'ftp' => [ + 'driver' => \Hyperf\Filesystem\Adapter\FtpAdapterFactory::class, + 'host' => 'ftp.example.com', + 'username' => 'username', + 'password' => 'password', + // 'port' => 21, + // 'root' => '/path/to/root', + // 'passive' => true, + // 'ssl' => true, + // 'timeout' => 30, + // 'ignorePassiveAddress' => false, + ], + 'memory' => [ + 'driver' => \Hyperf\Filesystem\Adapter\MemoryAdapterFactory::class, + ], + 's3' => [ + 'driver' => \Hyperf\Filesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => env('S3_KEY'), + 'secret' => env('S3_SECRET'), + ], + 'region' => env('S3_REGION'), + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => false, + 'endpoint' => env('S3_ENDPOINT'), + 'bucket_name' => env('S3_BUCKET'), + ], + 'minio' => [ + 'driver' => \Hyperf\Filesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => env('S3_KEY'), + 'secret' => env('S3_SECRET'), + ], + 'region' => env('S3_REGION'), + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => true, + 'endpoint' => env('S3_ENDPOINT'), + 'bucket_name' => env('S3_BUCKET'), + ], + 'oss' => [ + 'driver' => \Hyperf\Filesystem\Adapter\AliyunOssAdapterFactory::class, + 'accessId' => env('OSS_ACCESS_ID'), + 'accessSecret' => env('OSS_ACCESS_SECRET'), + 'bucket' => env('OSS_BUCKET'), + 'endpoint' => env('OSS_ENDPOINT'), + // 'timeout' => 3600, + // 'connectTimeout' => 10, + // 'isCName' => false, + // 'token' => '', + ], + 'qiniu' => [ + 'driver' => \Hyperf\Filesystem\Adapter\QiniuAdapterFactory::class, + 'accessKey' => env('QINIU_ACCESS_KEY'), + 'secretKey' => env('QINIU_SECRET_KEY'), + 'bucket' => env('QINIU_BUCKET'), + 'domain' => env('QINBIU_DOMAIN'), + ], + 'cos' => [ + 'driver' => \Hyperf\Filesystem\Adapter\CosAdapterFactory::class, + 'region' => env('COS_REGION'), + 'credentials' => [ + 'appId' => env('COS_APPID'), + 'secretId' => env('COS_SECRET_ID'), + 'secretKey' => env('COS_SECRET_KEY'), + ], + 'bucket' => env('COS_BUCKET'), + 'read_from_cdn' => false, + // 'timeout' => 60, + // 'connect_timeout' => 60, + // 'cdn' => '', + // 'scheme' => 'https', + ], + ], +]; diff --git a/config/autoload/server.php b/config/autoload/server.php index e0e6cd8..f44b083 100644 --- a/config/autoload/server.php +++ b/config/autoload/server.php @@ -36,16 +36,13 @@ return [ 'max_request' => 100000, 'socket_buffer_size' => 2 * 1024 * 1024, 'buffer_output_size' => 2 * 1024 * 1024, - // Task Worker 数量,根据您的服务器配置而配置适当的数量 'task_worker_num' => 8, - // 因为 `Task` 主要处理无法协程化的方法,所以这里推荐设为 `false`,避免协程下出现数据混淆的情况 'task_enable_coroutine' => false, ], 'callbacks' => [ SwooleEvent::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'], SwooleEvent::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'], SwooleEvent::ON_WORKER_EXIT => [Hyperf\Framework\Bootstrap\WorkerExitCallback::class, 'onWorkerExit'], - // Task callbacks SwooleEvent::ON_TASK => [Hyperf\Framework\Bootstrap\TaskCallback::class, 'onTask'], SwooleEvent::ON_FINISH => [Hyperf\Framework\Bootstrap\FinishCallback::class, 'onFinish'], ], diff --git a/config/routes.php b/config/routes.php index ae203c4..877fc1d 100644 --- a/config/routes.php +++ b/config/routes.php @@ -24,4 +24,7 @@ Router::addGroup('/v1/',function (){ Router::post('ServiceEvaluate/getPersonnelInfo', 'App\Controller\ServiceEvaluateController@getPersonnelInfo'); Router::post('ServiceEvaluate/getEvaluateList', 'App\Controller\ServiceEvaluateController@getEvaluateList'); Router::post('Store/applyEntry', 'App\Controller\StoreController@applyEntry'); + Router::post('Attachment/uploadImage', 'App\Controller\AttachmentController@uploadImage'); + Router::post('Attachment/uploadImageByBase64', 'App\Controller\AttachmentController@uploadImageByBase64'); + Router::post('Attachment/upload', 'App\Controller\AttachmentController@upload'); }); \ No newline at end of file