-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathOpenapiController.php
More file actions
172 lines (150 loc) · 5.5 KB
/
OpenapiController.php
File metadata and controls
172 lines (150 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<?php
namespace WebmanTech\Swagger\Controller;
use Exception;
use OpenApi\Annotations as OA;
use RuntimeException;
use Throwable;
use WebmanTech\CommonUtils\Local;
use WebmanTech\CommonUtils\Request;
use WebmanTech\CommonUtils\Response;
use WebmanTech\CommonUtils\View;
use WebmanTech\Swagger\Controller\RequiredElementsAttributes\PathItem\OpenapiSpec;
use WebmanTech\Swagger\DTO\ConfigOpenapiDocDTO;
use WebmanTech\Swagger\DTO\ConfigSwaggerUiDTO;
use WebmanTech\Swagger\Helper\ConfigHelper;
use WebmanTech\Swagger\Helper\JsExpression;
use WebmanTech\Swagger\Overwrite\Generator;
final class OpenapiController
{
private readonly array $requiredElements;
public function __construct()
{
$this->requiredElements = [
'info' => __DIR__ . '/RequiredElementsAttributes/Info',
'pathItem' => __DIR__ . '/RequiredElementsAttributes/PathItem',
];
}
/**
* Swagger UI 页面展示
* @throws Throwable
*/
public function swaggerUI(string $docRoute, ConfigSwaggerUiDTO|array $config = []): mixed
{
$config = ConfigSwaggerUiDTO::fromConfig($config);
// 支持通过 request 传参来强制重新生成
if ($request = Request::getCurrent()) {
$generate = (bool)($request->get('generate') ?? false);
if ($generate) {
$docRoute .= '?generate=1';
}
}
$data = $config->data;
$data['ui_config']['url'] = new JsExpression("window.location.pathname.replace(/\/+$/, '') + '/{$docRoute}'");
$data['dto_generator_url'] ??= null;
$data['dto_generator_config'] ??= [
'defaultGenerationType' => 'form',
'defaultNamespace' => 'app\\controller\\api\\form',
];
$content = View::renderPHP(Local::combinePath($config->view_path, $config->view), $data);
return Response::make()
->withHeaders(['Content-Type' => 'text/html; charset=utf-8'])
->withBody($content)
->getRaw();
}
/**
* Openapi 文档
* @throws Throwable
*/
public function openapiDoc(ConfigOpenapiDocDTO|array $config = []): mixed
{
$config = ConfigOpenapiDocDTO::fromConfig($config);
if ($config->max_execute_time) {
set_time_limit($config->max_execute_time);
}
if ($config->max_memory_usage) {
ini_set('memory_limit', $config->max_memory_usage . 'M');
}
$cache = $config->getCache();
$cacheKey = $config->getCacheKey();
$generate = false;
if ($request = Request::getCurrent()) {
$generate = (bool)($request->get('generate') ?? false);
}
$cached = $cache->get($cacheKey) ?? [null, null, null];
/** @var array{0: ?string, 1: ?string, 2: ?string} $cached */
[$content, $contentType, $md5] = $cached;
if ($md5 === null || $generate) {
$md5 = md5(serialize($config->scan_path) . $config->format);
try {
$generator = (new Generator($config))->init();
$config->applyGenerator($generator);
$openapi = $this->scanAndGenerateOpenapi($generator, $config->getScanSources(), validate: $config->openapi_validate);
$config->applyModify($openapi);
[$content, $contentType] = $config->generateWithFormat($openapi);
} catch (Throwable $e) {
if ($config->generate_error_handler) {
($config->generate_error_handler)($e);
}
throw $e;
}
$cache->set($cacheKey, [$content, $contentType, $md5]);
}
return Response::make()
->withHeaders(['Content-Type' => $contentType])
->withBody($content)
->getRaw();
}
/**
* DTO 生成器页面
*/
public function dtoGenerator(array|null $dtoGeneratorConfig = null): mixed
{
$path = ConfigHelper::getDtoGeneratorWebPath();
if ($path === null) {
throw new RuntimeException('DTO generator assets not found. Please install webman-tech/dto.');
}
$content = file_get_contents($path) ?: '';
if ($dtoGeneratorConfig) {
$dtoGeneratorConfig = json_encode($dtoGeneratorConfig);
$prefix = <<<JS
<script>
window.__DTO_GENERATOR_CONFIG = {$dtoGeneratorConfig};
</script>
JS;
$content = str_replace('</head>', $prefix . '</head>', $content);
}
return Response::make()
->withHeaders(['Content-Type' => 'text/html; charset=utf-8'])
->withBody($content)
->getRaw();
}
/**
* 扫描并生成 yaml
* @throws Throwable
*/
private function scanAndGenerateOpenapi(
Generator $generator,
iterable $scanSources,
bool $validate = false,
): OA\OpenApi
{
$openapi = $generator->generate(
[
$this->requiredElements,
$scanSources,
],
validate: false, // 固定为关闭,在后面再执行验证
);
if ($openapi === null) {
throw new Exception('openapi generate failed');
}
if (count($openapi->paths) > 1) {
// 表示已经有接口了,移除掉默认的必须路径
$openapi->paths = array_filter($openapi->paths, fn(OA\PathItem $pathItem) => $pathItem->path !== OpenapiSpec::EXAMPLE_PATH);
}
if ($validate) {
$openapi->validate();
}
return $openapi;
}
}