Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

支持绑定上传文件对象到控制器方法参数 #531

Merged
merged 3 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/base/version/2.0-2.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ v2.0 是一个非常成功的 LTS 版本,进行了底层重构,增加了强

* 模型查询构建器`Model::query()`、`Model::dbQuery()`支持定义表别名 ([文档](/v2.1/components/orm/RDModel.html))

* 新增[使用 Protobuf 的 gRPC HTTP 网关客户端](https://doc.imiphp.com/v2.1/components/rpc/grpc-proxy.html#%E4%BD%BF%E7%94%A8%20Protobuf%20%E7%9A%84%20gRPC%20HTTP%20%E7%BD%91%E5%85%B3%E5%AE%A2%E6%88%B7%E7%AB%AF)
* 新增[使用 Protobuf 的 gRPC HTTP 网关客户端](/v2.1/components/rpc/grpc-proxy.html#%E4%BD%BF%E7%94%A8%20Protobuf%20%E7%9A%84%20gRPC%20HTTP%20%E7%BD%91%E5%85%B3%E5%AE%A2%E6%88%B7%E7%AB%AF)

* `Imi\Util\Random` 新增 `float()` 和 `bytes()` 方法

Expand Down
4 changes: 4 additions & 0 deletions doc/components/httpserver/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ class Index extends HttpController
}
```

## 绑定请求参数到控制器方法参数

详见:[链接](/v2.1/components/httpserver/request.html#%E7%BB%91%E5%AE%9A%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0%E5%88%B0%E6%8E%A7%E5%88%B6%E5%99%A8%E6%96%B9%E6%B3%95%E5%8F%82%E6%95%B0)

## 属性

### $server
Expand Down
26 changes: 26 additions & 0 deletions doc/components/httpserver/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,32 @@ $swooleRequest = $this->request->getSwooleRequest();

## 绑定请求参数到控制器方法参数

### Action 方法参数

在控制器中,可以通过方法参数获取请求参数,imi 会自动绑定同名请求参数到方法参数。

imi `v2.1.47` 新增支持 `\Psr\Http\Message\UploadedFileInterface` 类型参数,可以直接获取上传的文件,并且 imi 底层会帮你做是否上传和成功的验证,失败会自动抛出异常。

```php
/**
* @Action
*/
public function requestParam1(string $string, int $int, float $float, bool $bool, \Psr\Http\Message\UploadedFileInterface $file): array
{
// 本地保存文件,$saveFileName 请改为自己的路径规则
$saveFileName = '/var/www/html/upload/' . uniqid(true) . '.' . pathinfo($file->getClientFilename(), \PATHINFO_EXTENSION);
$file->moveTo($saveFileName);

// 获取临时文件名,可用于对象存储上传
$tmpFileName = $file->getTmpFileName();

// 获取文件内容,可用于对象存储上传
$fileData = (string) $file->getStream();

return compact('string', 'int', 'float', 'bool');
}
```

### RequestParam 注解

imi `v2.1.27` 引入的新注解。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Imi\Util\Http\MessageUtil;
use Imi\Util\Stream\MemoryStream;
use Imi\Validate\Annotation\Required;
use Psr\Http\Message\UploadedFileInterface;
use Swoole\Coroutine;

/**
Expand Down Expand Up @@ -297,6 +298,22 @@ public function upload(): array
return $result;
}

/**
* @Action
*/
public function upload2(UploadedFileInterface $file): array
{
return [
'data' => [
'clientFilename' => $file->getClientFilename(),
'clientMediaType' => $file->getClientMediaType(),
'error' => $file->getError(),
'size' => $file->getSize(),
'hash' => md5($file->getStream()->getContents()),
],
];
}

/**
* @Action
*/
Expand Down
23 changes: 23 additions & 0 deletions src/Components/swoole/tests/unit/HttpServer/Tests/RequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,29 @@ public function testUploadMulti(): void
$this->assertEquals(md5($content), $file['hash']);
}

public function testUpload2(): void
{
$http = new HttpRequest();
$response = $http->post($this->host . 'upload2');
$data = $response->json(true);
$this->assertEquals('Missing uploaded file: file', $data['message'] ?? null);

$file = new UploadedFile(basename(__FILE__), MediaType::TEXT_HTML, __FILE__);
$http->content([
'file' => $file,
]);
$response = $http->post($this->host . 'upload2');
$data = $response->json(true);

$this->assertTrue(isset($data['data']));
$file = $data['data'];
$content = file_get_contents(__FILE__);
$this->assertEquals(basename(__FILE__), $file['clientFilename']);
$this->assertEquals(MediaType::TEXT_HTML, $file['clientMediaType']);
$this->assertEquals(\strlen($content), $file['size']);
$this->assertEquals(md5($content), $file['hash']);
}

/**
* 控制器不在服务器目录下的测试.
*/
Expand Down
15 changes: 15 additions & 0 deletions src/Server/Http/Middleware/ActionMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Imi\Util\ObjectArrayHelper;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

Expand Down Expand Up @@ -253,6 +254,20 @@ private function prepareActionParams(Request $request, RouteResult $routeResult)
{
$value = null;
}
elseif (($type = $actionMethodCacheItem->getType()) && (UploadedFileInterface::class === $type || is_subclass_of($type, UploadedFileInterface::class)))
{
$uploadedFiles ??= $request->getUploadedFiles();
if (!isset($uploadedFiles[$paramName]))
{
throw new \InvalidArgumentException(sprintf('Missing uploaded file: %s', $paramName));
}
/** @var UploadedFileInterface $value */
$value = $uploadedFiles[$paramName];
if (0 !== $value->getError())
{
throw new \RuntimeException(sprintf('Upload file failed. error:%d', $value->getError()));
}
}
else
{
throw new \InvalidArgumentException(sprintf('Missing parameter: %s', $paramName));
Expand Down