You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
7.9 KiB
258 lines
7.9 KiB
<?php |
|
|
|
namespace Plugins\ApiDoc\Service; |
|
|
|
use App\Middleware\Api\ApiAuthenticateMiddleware; |
|
use App\Util\Hy; |
|
use Hyperf\HttpServer\Annotation\Controller; |
|
use Hyperf\HttpServer\Annotation\GetMapping; |
|
use Hyperf\HttpServer\Annotation\Mapping; |
|
use Hyperf\HttpServer\Annotation\Middleware; |
|
use Hyperf\HttpServer\Annotation\Middlewares; |
|
use Hyperf\HttpServer\Annotation\PostMapping; |
|
use Plugins\ApiDoc\Attributes\Api; |
|
use Plugins\ApiDoc\Attributes\ApiBody; |
|
use Plugins\ApiDoc\Attributes\ApiQuery; |
|
use Plugins\ApiDoc\Attributes\ApiReturn; |
|
use Plugins\ApiDoc\Attributes\ApiVersion; |
|
use Sc\Util\ImitateAopProxy\AopProxyTrait; |
|
use Sc\Util\Tool; |
|
|
|
/** |
|
* Class ApiDocService |
|
*/ |
|
class ApiDocService |
|
{ |
|
use AopProxyTrait; |
|
|
|
/** |
|
* @throws \Exception |
|
*/ |
|
public function get(): array |
|
{ |
|
$appName = Hy::config('app_name', ''); |
|
$getPath = Hy::config('plugins.ApiDoc.get_path', []); |
|
|
|
$apiLists = $this->scan($getPath); |
|
|
|
return [ |
|
"title" => $appName . 'API 接口文档', |
|
"version" => '', |
|
'host' => Tool::url(Hy::request()->url())->getDomain(), |
|
'apiLists' => $apiLists, |
|
]; |
|
} |
|
|
|
/** |
|
* @param array $paths |
|
* |
|
* @return array |
|
* @throws \Exception |
|
*/ |
|
private function scan(array $paths): array |
|
{ |
|
$apiList = []; |
|
foreach ($paths as ['path' => $path, 'namespace' => $namespace]) { |
|
Tool::dir($path)->each(function (Tool\Dir\EachFile $eachFile) use ($namespace, &$apiList){ |
|
$classname = strtr(basename($eachFile->filename), ['.php' => '']); |
|
$classFullName = implode("\\", [$namespace, ...$eachFile->relativelyDirs, $classname]); |
|
try { |
|
$class = \Hyperf\Support\make($classFullName); |
|
if (!$class) return; |
|
|
|
$reflectionClass = $this->apiClassCheck($class); |
|
if ($reflectionClass) { |
|
$apiList[] = $this->apiResolve($reflectionClass); |
|
} |
|
} catch (\Throwable $throwable) {} |
|
}); |
|
} |
|
|
|
return $apiList; |
|
} |
|
|
|
/** |
|
* @param $class |
|
* |
|
* @return \ReflectionClass|null |
|
* @throws \ReflectionException |
|
*/ |
|
private function apiClassCheck($class): ?\ReflectionClass |
|
{ |
|
$reflexClass = new \ReflectionClass($class); |
|
|
|
if (!$reflexClass->getAttributes(Api::class) || !$reflexClass->getAttributes(Controller::class)) { |
|
return null; |
|
} |
|
|
|
return $reflexClass; |
|
} |
|
|
|
/** |
|
* @param \ReflectionClass $reflectionClass |
|
* |
|
* @return array |
|
*/ |
|
private function apiResolve(\ReflectionClass $reflectionClass): array |
|
{ |
|
$authenticateType = $this->authenticateType($reflectionClass); |
|
$groupTitle = $this->apiTitleResolve($reflectionClass->getDocComment()); |
|
$baseUri = $reflectionClass->getAttributes(Controller::class)[0]->newInstance()->prefix; |
|
|
|
$apis = []; |
|
foreach ($reflectionClass->getMethods() as $reflectionMethod) { |
|
if ($api = $this->methodResolve($reflectionMethod)) { |
|
$api['url'] = "/" . $baseUri . '/' . $api['url']; |
|
$api['auth'] = $api['auth'] ?: $authenticateType; |
|
$apis[] = $api; |
|
} |
|
} |
|
|
|
return [ |
|
'name' => $groupTitle, |
|
'children' => $apis, |
|
]; |
|
} |
|
|
|
/** |
|
* @param string $comment |
|
* |
|
* @return string |
|
*/ |
|
private function apiTitleResolve(string $comment): string |
|
{ |
|
preg_match('/^\/(\*|\s)+([^\*\s]+)/', $comment, $document); |
|
|
|
return empty($document[2]) ? "未命名" : $document[2]; |
|
} |
|
|
|
/** |
|
* @param \ReflectionClass|\ReflectionMethod $reflection |
|
* |
|
* @return string|null |
|
*/ |
|
private function authenticateType(\ReflectionClass|\ReflectionMethod $reflection): ?string |
|
{ |
|
$type = null; |
|
$Api = $reflection->getAttributes(Api::class); |
|
$Middlewares = $reflection->getAttributes(Middlewares::class); |
|
$Middleware = $reflection->getAttributes(Middleware::class); |
|
|
|
$haveMiddlewares = $Middlewares ? ($Middlewares[0]->newInstance()->middlewares ?: []) : []; |
|
$Middleware and $haveMiddlewares = [...$haveMiddlewares, ...array_map(fn($attribute) => $attribute->newInstance(), $Middleware)]; |
|
|
|
if ($haveMiddlewares){ |
|
foreach ($haveMiddlewares as $middleware) { |
|
if ($middleware->middleware === ApiAuthenticateMiddleware::class) { |
|
$type = "strength"; |
|
break; |
|
} |
|
} |
|
}else { |
|
if ($Api[0]->newInstance()->isAuthenticate){ |
|
$type = "weak"; |
|
} |
|
} |
|
|
|
return $type; |
|
} |
|
|
|
/** |
|
* @param \ReflectionMethod $reflectionMethod |
|
* |
|
* @return array|null |
|
*/ |
|
private function methodResolve(\ReflectionMethod $reflectionMethod): ?array |
|
{ |
|
if (!$reflectionMethod->getAttributes(Api::class)) { |
|
return null; |
|
} |
|
$api = [ |
|
'name' => $this->apiTitleResolve($reflectionMethod->getDocComment()), |
|
'call' => $this->getCallLocation($reflectionMethod), |
|
'url' => '', |
|
'method' => 'GET', |
|
'version' => [], |
|
'auth' => $this->authenticateType($reflectionMethod), |
|
'requestParams' => [], |
|
'responseParams' => [], |
|
]; |
|
|
|
foreach ($reflectionMethod->getAttributes() as $attribute) { |
|
$attribute = $attribute->newInstance(); |
|
switch (true) { |
|
case $attribute instanceof PostMapping: |
|
$api['method'] = 'POST'; |
|
case $attribute instanceof Mapping: |
|
$api['url'] = $attribute->path; |
|
break; |
|
case $attribute instanceof ApiQuery: |
|
case $attribute instanceof ApiBody: |
|
$api['requestParams'][] = json_decode(json_encode($attribute), true); |
|
break; |
|
case $attribute instanceof ApiReturn: |
|
$api['responseParams'][] = json_decode(json_encode($attribute), true); |
|
break; |
|
case $attribute instanceof ApiVersion: |
|
$api['version'][] = $attribute->version; |
|
} |
|
} |
|
$api['responseParams'] = $this->childrenParamHandle($api['responseParams']); |
|
if ($api['method'] === 'POST') { |
|
$api['requestParams'] = $this->childrenParamHandle($api['requestParams']); |
|
} |
|
|
|
return $api; |
|
} |
|
|
|
/** |
|
* @param array $initialData |
|
* |
|
* @return array |
|
*/ |
|
private function childrenParamHandle(array $initialData): array |
|
{ |
|
$newData = []; |
|
foreach ($initialData as $item) { |
|
$item['children'] = []; |
|
$names = explode('.', $item['name']); |
|
$current = &$newData; |
|
|
|
foreach (array_slice($names, 0, -1) as $attr) { |
|
$current = &$current[$attr]['children']; |
|
} |
|
|
|
$item['name'] = end($names); |
|
$current[$item['name']] = $item; |
|
} |
|
|
|
return $this->childrenData(array_values($newData)); |
|
} |
|
|
|
/** |
|
* @param array $data |
|
* |
|
* @return array |
|
*/ |
|
private function childrenData(array $data): array |
|
{ |
|
return array_map(function ($value){ |
|
if ($value['children']){ |
|
$value['children'] = array_values($this->childrenData($value['children'])); |
|
} |
|
return $value; |
|
}, $data); |
|
} |
|
|
|
/** |
|
* @param \ReflectionMethod $method |
|
* |
|
* @return string |
|
*/ |
|
private function getCallLocation(\ReflectionMethod $method): string |
|
{ |
|
return $method->getDeclaringClass()->getNamespaceName() . "\\" |
|
. $method->getDeclaringClass()->getShortName() . "@" |
|
. $method->getName(); |
|
} |
|
} |