|
|
|
<?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;
|
|
|
|
|
|
|
|
private array $config = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public function get(): array
|
|
|
|
{
|
|
|
|
$appName = Hy::config('app_name', '');
|
|
|
|
$getPath = $this->getDefaultConfig('get_path');
|
|
|
|
|
|
|
|
$apiLists = $this->scan($getPath);
|
|
|
|
|
|
|
|
return [
|
|
|
|
"title" => $appName . 'API 接口文档',
|
|
|
|
"version" => '',
|
|
|
|
'host' => Tool::url(Hy::request()->url())->getDomain(),
|
|
|
|
'apiLists' => $apiLists,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string|null $key
|
|
|
|
*
|
|
|
|
* @return array|mixed
|
|
|
|
*/
|
|
|
|
private function getDefaultConfig(string $key = null): mixed
|
|
|
|
{
|
|
|
|
if (!$this->config) {
|
|
|
|
$this->config = Hy::config('plugins.ApiDoc') ?: include __DIR__ . '/../config.php';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $key === null ? $this->config : $this->config[$key];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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();
|
|
|
|
}
|
|
|
|
}
|