Api文档
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

<?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();
}
}