代码功能更新
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\apidoc\library;
|
||||
|
||||
use app\service\ConfServiceFacade;
|
||||
use laytp\traits\Error;
|
||||
use plugin\apidoc\library\apidoc\library\Builder;
|
||||
use plugin\apidoc\library\apidoc\library\Extractor;
|
||||
use laytp\library\DirFile;
|
||||
use think\facade\Env;
|
||||
use think\facade\View;
|
||||
|
||||
class Apidoc
|
||||
{
|
||||
use Error;
|
||||
protected $template = 'index.html';
|
||||
protected $output = 'api.html';
|
||||
protected $force = true;
|
||||
|
||||
/**
|
||||
* 执行生成程序
|
||||
* @param $title
|
||||
* @return bool
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function execute($title)
|
||||
{
|
||||
// $addon = $input->getOption('addon');
|
||||
// if($addon){
|
||||
// $addon_service = new Addons();
|
||||
// $addon_info = $addon_service->_info->getPluginInfo($addon);
|
||||
// $controllerDir = Env::get('root_path') . DS . 'addons' . DS . $addon . DS . $addon_info['api_module'] . DS;
|
||||
// }else{
|
||||
$controllerDir = app()->getRootPath() . DS . 'app' . DS . 'controller' . DS . 'api' . DS;
|
||||
// }
|
||||
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($controllerDir), \RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
$weighs = [];
|
||||
$k = 0;
|
||||
foreach ($files as $name => $file) {
|
||||
if (!$file->isDir()) {
|
||||
$filePath = $file->getRealPath();
|
||||
$className = $this->getClassFromFile($filePath);
|
||||
$classAnnotations = Extractor::getClassAnnotations($className);
|
||||
if (isset($classAnnotations['ApiInternal'])) {
|
||||
continue;
|
||||
}
|
||||
$weigh = isset($classAnnotations['ApiWeigh']) ? intval($classAnnotations['ApiWeigh'][0]) : $k;
|
||||
$weighs[$this->getClassFromFile($filePath)] = $weigh;
|
||||
$k++;
|
||||
}
|
||||
}
|
||||
uasort($weighs, function ($a, $b) {
|
||||
if ($a == $b) return 0;
|
||||
return ($a > $b) ? -1 : 1;
|
||||
});
|
||||
|
||||
$classes = array_flip($weighs);
|
||||
|
||||
$builder = new Builder($classes);
|
||||
$apiDir = __DIR__ . DS . 'apidoc' . DS;
|
||||
$templateDir = $apiDir . 'template' . DS;
|
||||
$templateFile = $templateDir . $this->template;
|
||||
$var['plugin'] = '';
|
||||
$var['title'] = $title;
|
||||
$var['apiDomain'] = Env::get('domain.api');
|
||||
$var['createSignKey'] = ConfServiceFacade::get('system.basic.signKey');
|
||||
$var['apidocList'] = \plugin\apidoc\model\Apidoc::select()->toArray();
|
||||
// $content = $builder->render($templateFile, $var);
|
||||
$docslist = $builder->render($templateFile, $var);
|
||||
$menu = [];
|
||||
$menu[] = [
|
||||
'id' => 0,
|
||||
'title' => '全局说明',
|
||||
'docType' => 'meditor'
|
||||
];
|
||||
$key = 0;
|
||||
foreach($var['apidocList'] as $k=>$v){
|
||||
$key++;
|
||||
$temp = [];
|
||||
$temp['id'] = $key;
|
||||
$temp['title'] = $v['title'];
|
||||
$temp['docType'] = 'meditor';
|
||||
$menu[0]['children'][] = $temp;
|
||||
}
|
||||
foreach($docslist as $k=>$v){
|
||||
$key++;
|
||||
$temp = [];
|
||||
$temp['id'] = $key;
|
||||
$temp['title'] = $k;
|
||||
$temp['docType'] = 'api';
|
||||
foreach($v as $api){
|
||||
$key++;
|
||||
$children = [];
|
||||
$children['id'] = $key;
|
||||
$children['title'] = $api['title'];
|
||||
$children['docType'] = 'api';
|
||||
$temp['children'][] = $children;
|
||||
}
|
||||
$menu[] = $temp;
|
||||
}
|
||||
$menuOutputFile = app()->getRootPath() . 'public' . DS . 'static' . DS . 'admin' . DS . 'data' . DS . 'apidocMenu.json';
|
||||
file_put_contents($menuOutputFile, json_encode($menu, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
View::engine()->layout(false);
|
||||
$content = View::display(file_get_contents($templateFile), array_merge($var, ['docslist' => $docslist]));
|
||||
$outputDir = app()->getRootPath() . DS . 'public' . DS;
|
||||
$outputFile = $outputDir . $this->output;
|
||||
DirFile::createDir(dirname($outputFile));
|
||||
if (!file_put_contents($outputFile, $content)) {
|
||||
$this->setError('Cannot save the content to ' . $outputFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* get full qualified class name
|
||||
*
|
||||
* @param string $path_to_file
|
||||
* @return string
|
||||
* @author JBYRNE http://jarretbyrne.com/2015/06/197/
|
||||
*/
|
||||
protected function getClassFromFile($path_to_file)
|
||||
{
|
||||
//Grab the contents of the file
|
||||
$contents = file_get_contents($path_to_file);
|
||||
|
||||
//Start with a blank namespace and class
|
||||
$namespace = $class = "";
|
||||
|
||||
//Set helper values to know that we have found the namespace/class token and need to collect the string values after them
|
||||
$getting_namespace = $getting_class = false;
|
||||
|
||||
//Go through each token and evaluate it as necessary
|
||||
foreach (token_get_all($contents) as $token) {
|
||||
|
||||
//If this token is the namespace declaring, then flag that the next tokens will be the namespace name
|
||||
if (is_array($token) && $token[0] == T_NAMESPACE) {
|
||||
$getting_namespace = true;
|
||||
}
|
||||
|
||||
//If this token is the class declaring, then flag that the next tokens will be the class name
|
||||
if (is_array($token) && $token[0] == T_CLASS) {
|
||||
$getting_class = true;
|
||||
}
|
||||
|
||||
//While we're grabbing the namespace name...
|
||||
if ($getting_namespace === true) {
|
||||
|
||||
//If the token is a string or the namespace separator...
|
||||
if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {
|
||||
|
||||
//Append the token's value to the name of the namespace
|
||||
$namespace .= $token[1];
|
||||
} else if ($token === ';') {
|
||||
|
||||
//If the token is the semicolon, then we're done with the namespace declaration
|
||||
$getting_namespace = false;
|
||||
}
|
||||
}
|
||||
|
||||
//While we're grabbing the class name...
|
||||
if ($getting_class === true) {
|
||||
|
||||
//If the token is a string, it's the name of the class
|
||||
if (is_array($token) && $token[0] == T_STRING) {
|
||||
|
||||
//Store the token's value as the class name
|
||||
$class = $token[1];
|
||||
|
||||
//Got what we need, stope here
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Build the fully-qualified class name and return it
|
||||
return $namespace ? $namespace . '\\' . $class : $class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\apidoc\library\apidoc\library;
|
||||
|
||||
use think\facade\View;
|
||||
|
||||
/**
|
||||
* @website https://github.com/calinrada/php-apidoc
|
||||
* @author Calin Rada <rada.calin@gmail.com>
|
||||
* @author Karson <karsonzhang@163.com>
|
||||
*/
|
||||
class Builder
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
* @var \think\View
|
||||
*/
|
||||
public $view = null;
|
||||
|
||||
/**
|
||||
* parse classes
|
||||
* @var array
|
||||
*/
|
||||
protected $classes = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $classes
|
||||
*/
|
||||
public function __construct($classes = [])
|
||||
{
|
||||
$this->classes = array_merge($this->classes, $classes);
|
||||
}
|
||||
|
||||
protected function extractAnnotations()
|
||||
{
|
||||
$st_output = [];
|
||||
foreach ($this->classes as $class) {
|
||||
$st_output[] = Extractor::getAllClassAnnotations($class);
|
||||
}
|
||||
return end($st_output);
|
||||
}
|
||||
|
||||
protected function generateHeadersTemplate($docs)
|
||||
{
|
||||
if (!isset($docs['ApiHeaders'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$headerslist = [];
|
||||
foreach ($docs['ApiHeaders'] as $params) {
|
||||
$tr = [
|
||||
'name' => $params['name'],
|
||||
'type' => $params['type'],
|
||||
'sample' => isset($params['sample']) ? $params['sample'] : '',
|
||||
'required' => isset($params['required']) ? $params['required'] : false,
|
||||
'description' => isset($params['description']) ? $params['description'] : '',
|
||||
];
|
||||
$headerslist[] = $tr;
|
||||
}
|
||||
|
||||
return $headerslist;
|
||||
}
|
||||
|
||||
protected function generateParamsTemplate($docs)
|
||||
{
|
||||
if (!isset($docs['ApiParams'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$paramslist = [];
|
||||
foreach ($docs['ApiParams'] as $params) {
|
||||
$tr = [
|
||||
'name' => $params['name'],
|
||||
'type' => isset($params['type']) ? $params['type'] : 'string',
|
||||
'sample' => isset($params['sample']) ? $params['sample'] : '',
|
||||
'required' => isset($params['required']) ? $params['required'] : true,
|
||||
'description' => isset($params['description']) ? $params['description'] : '',
|
||||
];
|
||||
$paramslist[] = $tr;
|
||||
}
|
||||
|
||||
return $paramslist;
|
||||
}
|
||||
|
||||
protected function generateReturnHeadersTemplate($docs)
|
||||
{
|
||||
if (!isset($docs['ApiReturnHeaders'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$headerslist = [];
|
||||
foreach ($docs['ApiReturnHeaders'] as $params) {
|
||||
$tr = [
|
||||
'name' => $params['name'],
|
||||
'type' => 'string',
|
||||
'sample' => isset($params['sample']) ? $params['sample'] : '',
|
||||
'required' => isset($params['required']) && $params['required'] ? 'Yes' : 'No',
|
||||
'description' => isset($params['description']) ? $params['description'] : '',
|
||||
];
|
||||
$headerslist[] = $tr;
|
||||
}
|
||||
|
||||
return $headerslist;
|
||||
}
|
||||
|
||||
protected function generateReturnParamsTemplate($st_params)
|
||||
{
|
||||
if (!isset($st_params['ApiReturnParams'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$paramslist = [];
|
||||
foreach ($st_params['ApiReturnParams'] as $params) {
|
||||
$tr = [
|
||||
'name' => $params['name'],
|
||||
'type' => isset($params['type']) ? $params['type'] : 'string',
|
||||
'sample' => isset($params['sample']) ? $params['sample'] : '',
|
||||
'description' => isset($params['description']) ? $params['description'] : '',
|
||||
];
|
||||
$paramslist[] = $tr;
|
||||
}
|
||||
|
||||
return $paramslist;
|
||||
}
|
||||
|
||||
protected function generateBadgeForMethod($data)
|
||||
{
|
||||
$method = strtoupper(is_array($data['ApiMethod'][0]) ? $data['ApiMethod'][0]['data'] : $data['ApiMethod'][0]);
|
||||
$labes = [
|
||||
'POST' => 'label-primary',
|
||||
'GET' => 'label-success',
|
||||
'PUT' => 'label-warning',
|
||||
'DELETE' => 'label-danger',
|
||||
'PATCH' => 'label-default',
|
||||
'OPTIONS' => 'label-info',
|
||||
];
|
||||
|
||||
return isset($labes[$method]) ? $labes[$method] : $labes['GET'];
|
||||
}
|
||||
|
||||
public function parse()
|
||||
{
|
||||
$annotations = $this->extractAnnotations();
|
||||
|
||||
$counter = 0;
|
||||
$section = null;
|
||||
$docslist = [];
|
||||
foreach ($annotations as $class => $methods) {
|
||||
foreach ($methods as $name => $docs) {
|
||||
if (isset($docs['ApiSector'][0])) {
|
||||
$section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0];
|
||||
} else {
|
||||
$section = $class;
|
||||
}
|
||||
if (0 === count($docs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docslist[$section][] = [
|
||||
'id' => $counter,
|
||||
'title' => $docs['ApiTitle'][0],
|
||||
'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
|
||||
'method_label' => $this->generateBadgeForMethod($docs),
|
||||
'section' => $section,
|
||||
'route' => is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0],
|
||||
'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
|
||||
'body' => isset($docs['ApiBody'][0]) ? is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0] : '',
|
||||
'headerslist' => $this->generateHeadersTemplate($docs),
|
||||
'paramslist' => $this->generateParamsTemplate($docs),
|
||||
'returnheaderslist' => $this->generateReturnHeadersTemplate($docs),
|
||||
'returnparamslist' => $this->generateReturnParamsTemplate($docs),
|
||||
'return' => isset($docs['ApiReturn']) ? is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0] : '',
|
||||
];
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
return $docslist;
|
||||
}
|
||||
|
||||
public function getView()
|
||||
{
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染
|
||||
* @param $template
|
||||
* @param array $vars
|
||||
* @return array|string
|
||||
*/
|
||||
public function render($template, $vars = [])
|
||||
{
|
||||
$docslist = $this->parse();
|
||||
$ids = [];
|
||||
foreach ($docslist as $v) {
|
||||
foreach ($v as $api) {
|
||||
$ids[] = $api['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return $docslist;
|
||||
|
||||
return View::display(file_get_contents($template), array_merge($vars, ['docslist' => $docslist, 'ids' => json_encode($ids)]));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\apidoc\library\apidoc\library;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class imported from https://github.com/eriknyk/Annotations
|
||||
* @author Erik Amaru Ortiz https://github.com/eriknyk
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php The BSD License
|
||||
* @author Calin Rada <rada.calin@gmail.com>
|
||||
*/
|
||||
class Extractor
|
||||
{
|
||||
|
||||
/**
|
||||
* Static array to store already parsed annotations
|
||||
* @var array
|
||||
*/
|
||||
private static $annotationCache;
|
||||
|
||||
/**
|
||||
* Indicates that annotations should has strict behavior, 'false' by default
|
||||
* @var boolean
|
||||
*/
|
||||
private $strict = false;
|
||||
|
||||
/**
|
||||
* Stores the default namespace for Objects instance, usually used on methods like getMethodAnnotationsObjects()
|
||||
* @var string
|
||||
*/
|
||||
public $defaultNamespace = '';
|
||||
|
||||
/**
|
||||
* Sets strict variable to true/false
|
||||
* @param bool $value boolean value to indicate that annotations to has strict behavior
|
||||
*/
|
||||
public function setStrict($value)
|
||||
{
|
||||
$this->strict = (bool)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default namespace to use in object instantiation
|
||||
* @param string $namespace default namespace
|
||||
*/
|
||||
public function setDefaultNamespace($namespace)
|
||||
{
|
||||
$this->defaultNamespace = $namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets default namespace used in object instantiation
|
||||
* @return string $namespace default namespace
|
||||
*/
|
||||
public function getDefaultAnnotationNamespace()
|
||||
{
|
||||
return $this->defaultNamespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all anotations with pattern @SomeAnnotation() from a given class
|
||||
*
|
||||
* @param string $className class name to get annotations
|
||||
* @return array self::$annotationCache all annotated elements
|
||||
*/
|
||||
public static function getClassAnnotations($className)
|
||||
{
|
||||
if (!isset(self::$annotationCache[$className])) {
|
||||
$class = new \ReflectionClass($className);
|
||||
self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment());
|
||||
}
|
||||
|
||||
return self::$annotationCache[$className];
|
||||
}
|
||||
|
||||
public static function getAllClassAnnotations($className)
|
||||
{
|
||||
$class = new \ReflectionClass($className);
|
||||
|
||||
foreach ($class->getMethods() as $object) {
|
||||
self::$annotationCache['annotations'][$className][$object->name] = self::getMethodAnnotations($className, $object->name);
|
||||
}
|
||||
|
||||
return self::$annotationCache['annotations'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
|
||||
*
|
||||
* @param string $className class name
|
||||
* @param string $methodName method name to get annotations
|
||||
* @return array self::$annotationCache all annotated elements of a method given
|
||||
*/
|
||||
public static function getMethodAnnotations($className, $methodName)
|
||||
{
|
||||
if (!isset(self::$annotationCache[$className . '::' . $methodName])) {
|
||||
try {
|
||||
$method = new \ReflectionMethod($className, $methodName);
|
||||
$class = new \ReflectionClass($className);
|
||||
if ($className == $method->class) {
|
||||
if (!$method->isPublic() || $method->isConstructor()) {
|
||||
$annotations = [];
|
||||
} else {
|
||||
$annotations = self::consolidateAnnotations($method, $class);
|
||||
}
|
||||
} else {
|
||||
$annotations = [];
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
$annotations = [];
|
||||
}
|
||||
|
||||
self::$annotationCache[$className . '::' . $methodName] = $annotations;
|
||||
}
|
||||
|
||||
return self::$annotationCache[$className . '::' . $methodName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
|
||||
* and instance its abcAnnotation class
|
||||
*
|
||||
* @param string $className class name
|
||||
* @param string $methodName method name to get annotations
|
||||
* @return array self::$annotationCache all annotated objects of a method given
|
||||
*/
|
||||
public function getMethodAnnotationsObjects($className, $methodName)
|
||||
{
|
||||
$annotations = $this->getMethodAnnotations($className, $methodName);
|
||||
$objects = [];
|
||||
|
||||
$i = 0;
|
||||
|
||||
foreach ($annotations as $annotationClass => $listParams) {
|
||||
$annotationClass = ucfirst($annotationClass);
|
||||
$class = $this->defaultNamespace . $annotationClass . 'Annotation';
|
||||
|
||||
// verify is the annotation class exists, depending if Annotations::strict is true
|
||||
// if not, just skip the annotation instance creation.
|
||||
if (!class_exists($class)) {
|
||||
if ($this->strict) {
|
||||
throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class));
|
||||
} else {
|
||||
// silent skip & continue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($objects[$annotationClass])) {
|
||||
$objects[$annotationClass] = new $class();
|
||||
}
|
||||
|
||||
foreach ($listParams as $params) {
|
||||
if (is_array($params)) {
|
||||
foreach ($params as $key => $value) {
|
||||
$objects[$annotationClass]->set($key, $value);
|
||||
}
|
||||
} else {
|
||||
$objects[$annotationClass]->set($i++, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
private static function consolidateAnnotations($method, $class)
|
||||
{
|
||||
$dockblockClass = $class->getDocComment();
|
||||
$docblockMethod = $method->getDocComment();
|
||||
$methodName = $method->getName();
|
||||
|
||||
$methodAnnotations = self::parseAnnotations($docblockMethod);
|
||||
$classAnnotations = self::parseAnnotations($dockblockClass);
|
||||
if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$properties = $class->getDefaultProperties();
|
||||
$noNeedLogin = isset($properties['noNeedLogin']) ? is_array($properties['noNeedLogin']) ? $properties['noNeedLogin'] : [$properties['noNeedLogin']] : [];
|
||||
$noNeedRight = isset($properties['noNeedRight']) ? is_array($properties['noNeedRight']) ? $properties['noNeedRight'] : [$properties['noNeedRight']] : [];
|
||||
|
||||
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblockMethod), $methodArr);
|
||||
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $dockblockClass), $classArr);
|
||||
|
||||
$methodTitle = isset($methodArr[1]) && isset($methodArr[1][0]) ? $methodArr[1][0] : '';
|
||||
$classTitle = isset($classArr[1]) && isset($classArr[1][0]) ? $classArr[1][0] : '';
|
||||
|
||||
if (!isset($methodAnnotations['ApiMethod'])) {
|
||||
$methodAnnotations['ApiMethod'] = ['get'];
|
||||
}
|
||||
if (!isset($methodAnnotations['ApiSummary'])) {
|
||||
$methodAnnotations['ApiSummary'] = [$methodTitle];
|
||||
}
|
||||
if ($methodAnnotations) {
|
||||
foreach ($classAnnotations as $name => $valueClass) {
|
||||
if (count($valueClass) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($name === 'ApiRoute') {
|
||||
if (isset($methodAnnotations[$name])) {
|
||||
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . $methodAnnotations[$name][0]];
|
||||
} else {
|
||||
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . '/' . $method->getName()];
|
||||
}
|
||||
}
|
||||
|
||||
if ($name === 'ApiSector') {
|
||||
$methodAnnotations[$name] = $valueClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isset($methodAnnotations['ApiTitle'])) {
|
||||
$methodAnnotations['ApiTitle'] = [$methodTitle];
|
||||
}
|
||||
if (!isset($methodAnnotations['ApiRoute'])) {
|
||||
$urlArr = [];
|
||||
$className = $class->getName();
|
||||
|
||||
list($prefix, $suffix) = explode('\\controller\\', $className);
|
||||
$prefixArr = explode('\\', $prefix);
|
||||
$suffixArr = explode('\\', $suffix);
|
||||
if ($prefixArr[0] == 'app') {
|
||||
$prefixArr[0] = '';
|
||||
} else if ($prefixArr[0] == 'plugin') {
|
||||
$tempPrefixArr = [];
|
||||
$tempPrefixArr[0] = '';
|
||||
$tempPrefixArr[1] = $prefixArr[2];
|
||||
$prefixArr = $tempPrefixArr;
|
||||
}
|
||||
$urlArr = array_merge($urlArr, $prefixArr);
|
||||
$urlArr[] = implode('.', array_map(function ($item) {
|
||||
return self::parseName($item);
|
||||
}, $suffixArr));
|
||||
$urlArr[] = $method->getName();
|
||||
$methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
|
||||
}
|
||||
if (!isset($methodAnnotations['ApiSector'])) {
|
||||
$methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : [$classTitle];
|
||||
}
|
||||
if (!isset($methodAnnotations['ApiParams'])) {
|
||||
$params = self::parseCustomAnnotations($docblockMethod, 'param');
|
||||
foreach ($params as $k => $v) {
|
||||
$arr = explode(' ', preg_replace("/[\s]+/", " ", $v));
|
||||
$methodAnnotations['ApiParams'][] = [
|
||||
'name' => isset($arr[1]) ? str_replace('$', '', $arr[1]) : '',
|
||||
'nullable' => false,
|
||||
'type' => isset($arr[0]) ? $arr[0] : 'string',
|
||||
'description' => isset($arr[2]) ? $arr[2] : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
$methodAnnotations['ApiPermissionLogin'] = [!in_array('*', $noNeedLogin) && !in_array($methodName, $noNeedLogin)];
|
||||
$methodAnnotations['ApiPermissionRight'] = [!in_array('*', $noNeedRight) && !in_array($methodName, $noNeedRight)];
|
||||
return $methodAnnotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串命名风格转换
|
||||
* type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
|
||||
* @access public
|
||||
* @param string $name 字符串
|
||||
* @param integer $type 转换类型
|
||||
* @param bool $ucfirst 首字母是否大写(驼峰规则)
|
||||
* @return string
|
||||
*/
|
||||
private static function parseName($name, $type = 0, $ucfirst = true)
|
||||
{
|
||||
if ($type) {
|
||||
$name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
|
||||
return strtoupper($match[1]);
|
||||
}, $name);
|
||||
return $ucfirst ? ucfirst($name) : lcfirst($name);
|
||||
}
|
||||
|
||||
return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse annotations
|
||||
*
|
||||
* @param string $docblock
|
||||
* @param string $name
|
||||
* @return array parsed annotations params
|
||||
*/
|
||||
private static function parseCustomAnnotations($docblock, $name = 'param')
|
||||
{
|
||||
$annotations = [];
|
||||
|
||||
$docblock = substr($docblock, 3, -2);
|
||||
if (preg_match_all('/@' . $name . '(?:\s*(?:\(\s*)?(.*?)(?:\s*\))?)??\s*(?:\n|\*\/)/', $docblock, $matches)) {
|
||||
foreach ($matches[1] as $k => $v) {
|
||||
$annotations[] = $v;
|
||||
}
|
||||
}
|
||||
return $annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse annotations
|
||||
*
|
||||
* @param string $docblock
|
||||
* @return array parsed annotations params
|
||||
*/
|
||||
private static function parseAnnotations($docblock)
|
||||
{
|
||||
$annotations = [];
|
||||
|
||||
// Strip away the docblock header and footer to ease parsing of one line annotations
|
||||
$docblock = substr($docblock, 3, -2);
|
||||
if (preg_match_all('/@(?<name>[A-Za-z_-]+)[\s\t]*\((?<args>(?:(?!\)).)*)\)\r?/s', $docblock, $matches)) {
|
||||
$numMatches = count($matches[0]);
|
||||
for ($i = 0; $i < $numMatches; ++$i) {
|
||||
// annotations has arguments
|
||||
if (isset($matches['args'][$i])) {
|
||||
$argsParts = trim($matches['args'][$i]);
|
||||
$name = $matches['name'][$i];
|
||||
if ($name == 'ApiReturn') {
|
||||
$value = $argsParts;
|
||||
} else {
|
||||
$argsParts = preg_replace("/\{(\w+)\}/", '#$1#', $argsParts);
|
||||
$value = self::parseArgs($argsParts);
|
||||
if (is_string($value)) {
|
||||
$value = preg_replace("/\#(\w+)\#/", '{$1}', $argsParts);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$value = [];
|
||||
}
|
||||
|
||||
$annotations[$name][] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse individual annotation arguments
|
||||
*
|
||||
* @param string $content arguments string
|
||||
* @return array annotated arguments
|
||||
*/
|
||||
private static function parseArgs($content)
|
||||
{
|
||||
// Replace initial stars
|
||||
$content = preg_replace('/^\s*\*/m', '', $content);
|
||||
|
||||
$data = [];
|
||||
$len = strlen($content);
|
||||
$i = 0;
|
||||
$var = '';
|
||||
$val = '';
|
||||
$level = 1;
|
||||
|
||||
$prevDelimiter = '';
|
||||
$nextDelimiter = '';
|
||||
$nextToken = '';
|
||||
$composing = false;
|
||||
$type = 'plain';
|
||||
$delimiter = null;
|
||||
$quoted = false;
|
||||
$tokens = ['"', '"', '{', '}', ',', '='];
|
||||
|
||||
while ($i <= $len) {
|
||||
$prev_c = substr($content, $i - 1, 1);
|
||||
$c = substr($content, $i++, 1);
|
||||
|
||||
if ($c === '"' && $prev_c !== "\\") {
|
||||
$delimiter = $c;
|
||||
//open delimiter
|
||||
if (!$composing && empty($prevDelimiter) && empty($nextDelimiter)) {
|
||||
$prevDelimiter = $nextDelimiter = $delimiter;
|
||||
$val = '';
|
||||
$composing = true;
|
||||
$quoted = true;
|
||||
} else {
|
||||
// close delimiter
|
||||
if ($c !== $nextDelimiter) {
|
||||
throw new Exception(sprintf(
|
||||
"Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
|
||||
));
|
||||
}
|
||||
|
||||
// validating syntax
|
||||
if ($i < $len) {
|
||||
if (',' !== substr($content, $i, 1) && '\\' !== $prev_c) {
|
||||
throw new Exception(sprintf(
|
||||
"Parse Error: missing comma separator near: ...%s<--", substr($content, ($i - 10), $i)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$prevDelimiter = $nextDelimiter = '';
|
||||
$composing = false;
|
||||
$delimiter = null;
|
||||
}
|
||||
} elseif (!$composing && in_array($c, $tokens)) {
|
||||
switch ($c) {
|
||||
case '=':
|
||||
$prevDelimiter = $nextDelimiter = '';
|
||||
$level = 2;
|
||||
$composing = false;
|
||||
$type = 'assoc';
|
||||
$quoted = false;
|
||||
break;
|
||||
case ',':
|
||||
$level = 3;
|
||||
|
||||
// If composing flag is true yet,
|
||||
// it means that the string was not enclosed, so it is parsing error.
|
||||
if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) {
|
||||
throw new Exception(sprintf(
|
||||
"Parse Error: enclosing error -> expected: [%s], given: [%s]", $nextDelimiter, $c
|
||||
));
|
||||
}
|
||||
|
||||
$prevDelimiter = $nextDelimiter = '';
|
||||
break;
|
||||
case '{':
|
||||
$subc = '';
|
||||
$subComposing = true;
|
||||
|
||||
while ($i <= $len) {
|
||||
$c = substr($content, $i++, 1);
|
||||
|
||||
if (isset($delimiter) && $c === $delimiter) {
|
||||
throw new Exception(sprintf(
|
||||
"Parse Error: Composite variable is not enclosed correctly."
|
||||
));
|
||||
}
|
||||
|
||||
if ($c === '}') {
|
||||
$subComposing = false;
|
||||
break;
|
||||
}
|
||||
$subc .= $c;
|
||||
}
|
||||
|
||||
// if the string is composing yet means that the structure of var. never was enclosed with '}'
|
||||
if ($subComposing) {
|
||||
throw new Exception(sprintf(
|
||||
"Parse Error: Composite variable is not enclosed correctly. near: ...%s'", $subc
|
||||
));
|
||||
}
|
||||
|
||||
$val = self::parseArgs($subc);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if ($level == 1) {
|
||||
$var .= $c;
|
||||
} elseif ($level == 2) {
|
||||
$val .= $c;
|
||||
}
|
||||
}
|
||||
|
||||
if ($level === 3 || $i === $len) {
|
||||
if ($type == 'plain' && $i === $len) {
|
||||
$data = self::castValue($var);
|
||||
} else {
|
||||
$data[trim($var)] = self::castValue($val, !$quoted);
|
||||
}
|
||||
|
||||
$level = 1;
|
||||
$var = $val = '';
|
||||
$composing = false;
|
||||
$quoted = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try determinate the original type variable of a string
|
||||
*
|
||||
* @param string $val string containing possibles variables that can be cast to bool or int
|
||||
* @param boolean $trim indicate if the value passed should be trimmed after to try cast
|
||||
* @return mixed returns the value converted to original type if was possible
|
||||
*/
|
||||
private static function castValue($val, $trim = false)
|
||||
{
|
||||
if (is_array($val)) {
|
||||
foreach ($val as $key => $value) {
|
||||
$val[$key] = self::castValue($value);
|
||||
}
|
||||
} elseif (is_string($val)) {
|
||||
if ($trim) {
|
||||
$val = trim($val);
|
||||
}
|
||||
$val = stripslashes($val);
|
||||
$tmp = strtolower($val);
|
||||
|
||||
if ($tmp === 'false' || $tmp === 'true') {
|
||||
$val = $tmp === 'true';
|
||||
} elseif (is_numeric($val)) {
|
||||
return $val + 0;
|
||||
}
|
||||
|
||||
unset($tmp);
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,744 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Cache" content="no-cache">
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>Api文档</title>
|
||||
<!-- 依 赖 样 式 -->
|
||||
<link rel="stylesheet" href="/static/component/laytp/css/laytp.css" />
|
||||
<!-- 加 载 样 式-->
|
||||
<link rel="stylesheet" href="/static/admin/css/load.css" />
|
||||
<!-- 布 局 样 式 -->
|
||||
<link rel="stylesheet" href="/static/admin/css/apidoc.css" />
|
||||
</head>
|
||||
<style>
|
||||
.api-page-tab-content {
|
||||
background-color: #fff;
|
||||
overflow: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.api-url {
|
||||
/*position: relative;*/
|
||||
color: rgb(221, 17, 68);
|
||||
background: #f6f6f6;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.layui-form-pane .layui-form-label {
|
||||
width: 280px;
|
||||
/*height: 34px;*/
|
||||
padding: 6px 15px;
|
||||
}
|
||||
|
||||
.layui-form-pane .layui-input-block {
|
||||
margin-left: 280px;
|
||||
}
|
||||
|
||||
.editormd-preview-container, .editormd-html-preview {
|
||||
width: 98% !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
/* 使md编辑器中的li取消样式none */
|
||||
list-style: unset;
|
||||
}
|
||||
|
||||
.api-tab-title {
|
||||
position: fixed;
|
||||
left: 230px;
|
||||
right: 0;
|
||||
z-index: 999999;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.layui-tab-bar{
|
||||
display:none;
|
||||
}
|
||||
</style>
|
||||
<!-- 结 构 代 码 -->
|
||||
<body class="layui-layout-body laytp-admin">
|
||||
<!-- 布 局 框 架 -->
|
||||
<div class="layui-layout layui-layout-admin">
|
||||
<div class="layui-header">
|
||||
<!-- 顶 部 左 侧 功 能 -->
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<li class="collaspe layui-nav-item"><a href="javascript:void(0);" class="layui-icon layui-icon-shrink-right"></a></li>
|
||||
</ul>
|
||||
<!-- 顶 部 右 侧 菜 单 -->
|
||||
<div id="control" class="layui-layout-control"></div>
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<li class="layui-nav-item layui-hide-xs"><a href="javascript:void(0);" class="fullScreen layui-icon layui-icon-screen-full"></a></li>
|
||||
<!-- 主 题 配 置 -->
|
||||
<li class="layui-nav-item apiSet"><a href="javascript:void(0);" class="layui-icon layui-icon-set"></a></li>
|
||||
<li class="layui-nav-item setting"><a href="javascript:void(0);" class="layui-icon layui-icon-more-vertical"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- 侧 边 区 域 -->
|
||||
<div class="layui-side layui-bg-black">
|
||||
<!-- 菜 单 顶 部 -->
|
||||
<div class="layui-logo">
|
||||
<!-- 图 标 -->
|
||||
<img class="logo" />
|
||||
<!-- 标 题 -->
|
||||
<span class="title"></span>
|
||||
</div>
|
||||
<!-- 菜 单 内 容 -->
|
||||
<div>
|
||||
<ul class="layui-nav arrow layui-nav-tree laytp-nav-tree" style="display: block;">
|
||||
<li class="layui-nav-item">
|
||||
<div class="search" style="padding: 5px;">
|
||||
<input type="text" class="layui-input search-menu" placeholder="搜索文档">
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="layui-side-scroll">
|
||||
<div id="sideMenu"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 视 图 页 面 -->
|
||||
<div class="layui-body">
|
||||
{php}$key = 0;{/php}
|
||||
{foreach $apidocList as $k=>$v}
|
||||
{php}$key++;{/php}
|
||||
<div class="api-page-tab-content" id="meditor_{$key}" style="display:none;">
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title api-tab-title">
|
||||
<li class="layui-this">
|
||||
{$v.title}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content meditorContent api-content" id="meditor_{$v.id}_content">
|
||||
<textarea style="display:none;">{$v.des}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
{foreach $docslist as $k=>$v}
|
||||
{php}$key++;{/php}
|
||||
{foreach $v as $ak=>$api}
|
||||
{php}$key++;{/php}
|
||||
<div class="api-page-tab-content" id="api_{$key}" style="display:none;">
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title api-tab-title">
|
||||
<li class="layui-this">接口说明</li>
|
||||
<li>在线测试</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content markdown-body api-content">
|
||||
<div class="layui-tab-item layui-show editormd-html-preview">
|
||||
<p><strong>简要说明</strong></p>
|
||||
<ul>
|
||||
<li>{$api.summary}</li>
|
||||
</ul>
|
||||
<p><strong>请求方式</strong></p>
|
||||
<ul>
|
||||
<li><code class="api-url">{$api.method}</code></li>
|
||||
</ul>
|
||||
<p><strong>请求地址</strong></p>
|
||||
<ul>
|
||||
<li><code class="api-url" route="{if $apiDomain && substr($api['route'],0,5) === '/api.'}/{:substr($api['route'],5)}{else /}{$api.route}{/if}">{if $apiDomain && substr($api['route'],0,5) === '/api.'}/{:substr($api['route'],5)}{else /}{$api.route}{/if}</code></li>
|
||||
</ul>
|
||||
<p><strong>Headers参数</strong></p>
|
||||
{if $api.headerslist}
|
||||
<div style="width: 100%;overflow-x: auto;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left;">名称</th>
|
||||
<th style="text-align: left;">类型</th>
|
||||
<th style="text-align: left;">必选</th>
|
||||
<th style="text-align: left;">描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach $api.headerslist as $hk=>$hv}
|
||||
<tr>
|
||||
<td style="text-align:left">{$hv.name}</td>
|
||||
<td style="text-align:left">{$hv.type}</td>
|
||||
<td style="text-align:left">{if $hv.required}是{else}否{/if}</td>
|
||||
<td>{$hv.description}</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{else}
|
||||
<ul>
|
||||
<li>无</li>
|
||||
</ul>
|
||||
{/if}
|
||||
<p><strong>Body参数</strong></p>
|
||||
{if $api.paramslist}
|
||||
<div style="width: 100%;overflow-x: auto;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left;">名称</th>
|
||||
<th style="text-align: left;">类型</th>
|
||||
<th style="text-align: left;">必选</th>
|
||||
<th style="text-align: left;">描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach $api.paramslist as $pk=>$pv}
|
||||
<tr>
|
||||
<td style="text-align:left">{$pv.name}</td>
|
||||
<td style="text-align:left">{$pv.type}</td>
|
||||
<td style="text-align:left">{if $pv.required}是{else}否{/if}</td>
|
||||
<td>{if $pv.sample}{$pv.description} -
|
||||
例:{$pv.sample}{else}{$pv.description}{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{else}
|
||||
<ul>
|
||||
<li>无</li>
|
||||
</ul>
|
||||
{/if}
|
||||
<p><strong>返回示例</strong></p>
|
||||
{if $api.return}
|
||||
<pre><code class="json">{$api.return}</code></pre>
|
||||
{else}
|
||||
<ul>
|
||||
<li>无</li>
|
||||
</ul>
|
||||
{/if}
|
||||
<p><strong>返回说明</strong></p>
|
||||
{if $api.returnparamslist}
|
||||
<div style="width: 100%;overflow-x: auto;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left;">名称</th>
|
||||
<th style="text-align: left;">类型</th>
|
||||
<th style="text-align: left;">描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach $api.returnparamslist as $pk=>$pv}
|
||||
<tr>
|
||||
<td style="text-align:left">{$pv.name}</td>
|
||||
<td style="text-align:left">{$pv.type}</td>
|
||||
<td style="text-align:left">{$pv.description}</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{else}
|
||||
<ul>
|
||||
<li>无</li>
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="layui-tab-item editormd-html-preview">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header" style="background-color: #F2F2F2;">Body传参</div>
|
||||
<div class="layui-card-body main-container">
|
||||
<form class="layui-form layui-form-pane" method="{$api.method}"
|
||||
route="{if $apiDomain && substr($api['route'],0,5) === '/api.'}/{:substr($api['route'],5)}{else /}{$api.route}{/if}"
|
||||
lay-filter="test_inline_{$key}" id="test_inline_{$key}">
|
||||
{foreach $api.paramslist as $pk=>$pv}
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">{$pv.name}</label>
|
||||
<div class="layui-input-block">
|
||||
{if($pv.type == 'array')}
|
||||
<table class="layui-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="right" style="width:400px">
|
||||
<input type="text" class="layui-input" name="{$pv.name}[]" autocomplete="off" placeholder="{if $pv.sample}{$pv.description} - 例:{$pv.sample}{else}请输入{$pv.description}{/if}" />
|
||||
</td>
|
||||
<td>
|
||||
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-array-param"></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-add-1 add-array-param" data-field="{$pv.name}">追加参数</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{else}
|
||||
<input type="{if ($pv.type == 'file')}file{else}text{/if}"
|
||||
name="{$pv.name}" autocomplete="off"
|
||||
placeholder="{if $pv.sample}{$pv.description} - 例:{$pv.sample}{else}请输入{$pv.description}{/if}"
|
||||
class="layui-input">
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button type="button" class="laytp-btn laytp-btn-primary"
|
||||
href="javascript:void(0);"
|
||||
onclick="api.testInline({$key})">提 交
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card" id="response_card_{$key}">
|
||||
<div class="layui-card-header" style="background-color: #F2F2F2;">响应输出</div>
|
||||
<div class="layui-card-body">
|
||||
<pre><code class="json" id="response_{$key}"></code></pre>
|
||||
<pre class="layui-code" id="response_headers_{$key}"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/foreach}
|
||||
{/foreach}
|
||||
</div>
|
||||
<!-- 遮 盖 层 -->
|
||||
<div class="laytp-cover"></div>
|
||||
<!-- 加 载 动 画-->
|
||||
<div class="loader-main">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 移 动 端 便 捷 操 作 -->
|
||||
<div class="laytp-collasped-pe collaspe">
|
||||
<a href="javascript:void(0);" class="layui-icon layui-icon-shrink-right"></a>
|
||||
</div>
|
||||
|
||||
<!-- 请求配置弹出层内容开始 -->
|
||||
<script type="text/html" id="set">
|
||||
<div class="popup-content">
|
||||
<div class="api-page-tab-content">
|
||||
<form class="layui-form" lay-filter="setDataForm" id="setDataForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">Api请求域名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="apiUrl"
|
||||
placeholder="完整的请求前缀,以http://或者https://开头,不以/结尾" autocomplete="off" class="layui-input"
|
||||
value="{{d.apiUrl}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" title="开关">签名验证</label>
|
||||
<div class="layui-input-block">
|
||||
<!-- 隐藏域设置开关未选中时需要传递的参数值 -->
|
||||
<input type="hidden" name="createSign" id="createSign-2" value="2"/>
|
||||
<input type="checkbox" name="createSign" id="createSign-1" lay-skin="switch" lay-text="验证|不验证"
|
||||
{{# if(d.createSign== 1){ }}checked="checked" {{# } }} value="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" title="Header参数">Header参数</label>
|
||||
<div class="layui-input-block">
|
||||
<table class="layui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数Key</th>
|
||||
<th>参数Value</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<input type="text" class="layui-input" value="token" readonly="readonly"/>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="layui-input" name="token" value="{{d.headerJson.token}}"
|
||||
autocomplete="off"/>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{# let k; }}
|
||||
{{# for(k in d.headerJson){ }}
|
||||
{{# if(k !== "token" && k !== "sign" && k !== "request-time"){ }}
|
||||
<tr>
|
||||
<td align="right">
|
||||
<input type="text" class="layui-input" name="header[key][]" value="{{k}}"
|
||||
autocomplete="off"/>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="layui-input" name="header[value][]"
|
||||
value="{{d.headerJson[k]}}" autocomplete="off"/>
|
||||
</td>
|
||||
<td>
|
||||
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-header-param"></a>
|
||||
</td>
|
||||
</tr>
|
||||
{{# } }}
|
||||
{{# } }}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-add-1 add-header-param">追加参数</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<!-- 请求配置弹出层内容结束 -->
|
||||
|
||||
<script type="text/html" id="arrayParam">
|
||||
<table class="layui-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<input type="text" class="layui-input" name="{{d.field}}[]" autocomplete="off"/>
|
||||
</td>
|
||||
<td>
|
||||
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-array-param"></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-add-1 add-array-param">追加参数</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<!-- 依 赖 脚 本 -->
|
||||
<script type="text/javascript" src="/static/component/jquery_3.3.1.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="/static/component/jquery.serializejson.min.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="/static/component/highlight/highlight.pack.js" charset="utf-8"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/component/highlight/styles/docco.css"/>
|
||||
|
||||
<link rel="stylesheet" href="/static/plugin/meditor/css/editormd.css"/>
|
||||
<script src="/static/plugin/meditor/lib/marked.min.js"></script>
|
||||
<script src="/static/plugin/meditor/lib/prettify.min.js"></script>
|
||||
<script src="/static/plugin/meditor/lib/flowchart.min.js"></script>
|
||||
<script src="/static/plugin/meditor/lib/raphael.min.js"></script>
|
||||
<script src="/static/plugin/meditor/lib/underscore.min.js"></script>
|
||||
<script src="/static/plugin/meditor/lib/sequence-diagram.min.js"></script>
|
||||
<script src="/static/plugin/meditor/lib/jquery.flowchart.min.js"></script>
|
||||
<script src="/static/plugin/meditor/editormd.min.js"></script>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.18.0/js/md5.js"></script>
|
||||
|
||||
<script>
|
||||
// 初始化静态文件版本号
|
||||
localStorage.setItem("version","1.0.2.Release");
|
||||
document.write("<script src='/static/component/layui/layui.js?v="+localStorage.getItem("version")+"'><\/script>");
|
||||
document.write("<script src='/static/component/laytp/layuiConfig.js?v="+localStorage.getItem("version")+"'><\/script>");
|
||||
</script>
|
||||
<!-- 框 架 初 始 化 -->
|
||||
<script>
|
||||
//定义storage
|
||||
let storage = (function () {
|
||||
let storage;
|
||||
try {
|
||||
storage = window.localStorage;
|
||||
return storage;
|
||||
} catch (exception) {
|
||||
return false;
|
||||
}
|
||||
}());
|
||||
layui.use(['laytp','apidoc','popup'], function() {
|
||||
let createSignKey = "{$createSignKey}";
|
||||
let apiDomain = "{$apiDomain}";
|
||||
let apiUrl = (apiDomain.length > 0) ? apiDomain : window.location.protocol + "//" + window.location.host;
|
||||
|
||||
//请求配置
|
||||
let nowSetJson = storage.getItem("setJson") ? JSON.parse(storage.getItem("setJson")) : {
|
||||
apiUrl: apiUrl,
|
||||
createSign: 2,
|
||||
headerJson: {token: ""}
|
||||
};
|
||||
|
||||
//渲染所有的meditor编辑器内容
|
||||
layui.each($(".meditorContent"), function (key, item) {
|
||||
editormd.markdownToHTML($(item).attr('id'), {
|
||||
htmlDecode: "style,script,iframe", // you can filter tags decode
|
||||
emoji: true,
|
||||
taskList: true,
|
||||
tex: true, // 默认不解析
|
||||
flowChart: true, // 默认不解析
|
||||
sequenceDiagram: true, // 默认不解析
|
||||
});
|
||||
});
|
||||
//当表格列数过长时将自动出现滚动条
|
||||
var themeColorContext = layui.context.get('theme-color-context');
|
||||
$.each($('table'), function (i, item) {
|
||||
if($(this).attr('class') !== 'layui-table'){
|
||||
$('thead > tr', this).attr('style', 'background-color: '+themeColorContext+';color:white;');
|
||||
$(this).prop('outerHTML', '<div style="width: 95%;overflow-x: auto;">' + $(this).prop('outerHTML') + '</div>');
|
||||
}
|
||||
});
|
||||
|
||||
//弹窗展示请求配置
|
||||
$(document).off("click", ".apiSet").on("click", ".apiSet", function () {
|
||||
let layerDiv = layui.layer.open({
|
||||
type: 1,
|
||||
offset: 'r',
|
||||
area: ['600px', '100%'],
|
||||
title: "请求配置",
|
||||
content: layui.laytpl($("#set").html()).render(nowSetJson),
|
||||
shade: 0.1,
|
||||
closeBtn: 0,
|
||||
shadeClose: false,
|
||||
anim: -1,
|
||||
skin: 'layer-anim-right',
|
||||
move: false,
|
||||
success: function (layero, index) {
|
||||
$(document).off("click", '#layui-layer-shade' + index).on("click", '#layui-layer-shade' + index, function () {
|
||||
var $layero = $('#layui-layer' + index);
|
||||
$layero.animate({
|
||||
left: $layero.offset().left + $layero.width()
|
||||
}, 200, function() {
|
||||
let formJson = $('#setDataForm').serializeJSON();
|
||||
let headerJson = {
|
||||
token: formJson.token
|
||||
};
|
||||
let keyJson = {};
|
||||
if (formJson.hasOwnProperty("header") && formJson.header.hasOwnProperty("key")) {
|
||||
keyJson = formJson.header.key;
|
||||
}
|
||||
let valueJson = {};
|
||||
if (formJson.hasOwnProperty("header") && formJson.header.hasOwnProperty("value")) {
|
||||
valueJson = formJson.header.value;
|
||||
}
|
||||
|
||||
$.each(keyJson, function (key, item) {
|
||||
if(keyJson[key]){
|
||||
headerJson[keyJson[key]] = valueJson[key];
|
||||
}
|
||||
});
|
||||
|
||||
let setJson = {
|
||||
apiUrl: formJson.apiUrl,
|
||||
createSign: formJson.createSign,
|
||||
headerJson: headerJson
|
||||
};
|
||||
|
||||
nowSetJson = setJson;
|
||||
|
||||
storage.setItem("setJson", JSON.stringify(setJson));
|
||||
|
||||
facade.success("保存成功");
|
||||
|
||||
layer.close(index);
|
||||
});
|
||||
});
|
||||
},
|
||||
btn: ['保 存', '取 消'],
|
||||
yes: function (index, layero) {
|
||||
let formJson = $('#setDataForm').serializeJSON();
|
||||
let headerJson = {
|
||||
token: formJson.token
|
||||
};
|
||||
let keyJson = {};
|
||||
if (formJson.hasOwnProperty("header") && formJson.header.hasOwnProperty("key")) {
|
||||
keyJson = formJson.header.key;
|
||||
}
|
||||
let valueJson = {};
|
||||
if (formJson.hasOwnProperty("header") && formJson.header.hasOwnProperty("value")) {
|
||||
valueJson = formJson.header.value;
|
||||
}
|
||||
|
||||
$.each(keyJson, function (key, item) {
|
||||
if(keyJson[key]){
|
||||
headerJson[keyJson[key]] = valueJson[key];
|
||||
}
|
||||
});
|
||||
|
||||
let setJson = {
|
||||
apiUrl: formJson.apiUrl,
|
||||
createSign: formJson.createSign,
|
||||
headerJson: headerJson
|
||||
};
|
||||
|
||||
nowSetJson = setJson;
|
||||
|
||||
storage.setItem("setJson", JSON.stringify(setJson));
|
||||
|
||||
facade.success("保存成功");
|
||||
|
||||
layer.close(index);
|
||||
}
|
||||
});
|
||||
layui.laytpForm.render("#layui-layer" + layerDiv);
|
||||
});
|
||||
|
||||
//追加Header参数
|
||||
$(document).off("click", ".add-header-param").on("click", ".add-header-param", function () {
|
||||
let clickObj = $(this);
|
||||
let template = '<tr>' +
|
||||
'<td align="right">' +
|
||||
'<input type="text" class="layui-input" name="header[key][]" autocomplete="off" />' +
|
||||
'</td>' +
|
||||
'<td>' +
|
||||
'<input type="text" class="layui-input" name="header[value][]" autocomplete="off" />' +
|
||||
'</td>' +
|
||||
'<td>' +
|
||||
'<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-header-param"></a>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
clickObj.parent().parent().before(template);
|
||||
});
|
||||
|
||||
//追加数组参数
|
||||
$(document).off("click", ".add-array-param").on("click", ".add-array-param", function () {
|
||||
let clickObj = $(this);
|
||||
let field = clickObj.data('field');
|
||||
let template = '<tr>' +
|
||||
'<td align="right">' +
|
||||
'<input type="text" class="layui-input" name="' + field + '[]" autocomplete="off" />' +
|
||||
'</td>' +
|
||||
'<td>' +
|
||||
'<a class="layui-btn layui-btn-primary layui-btn-sm layui-icon layui-icon-delete del-array-param"></a>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
clickObj.parent().parent().before(template);
|
||||
});
|
||||
|
||||
//删除header参数
|
||||
$(document).off("click", ".del-header-param").on("click", ".del-header-param", function () {
|
||||
let clickObj = $(this);
|
||||
clickObj.parent().parent().remove();
|
||||
});
|
||||
|
||||
//删除array参数
|
||||
$(document).off("click", ".del-array-param").on("click", ".del-array-param", function () {
|
||||
let clickObj = $(this);
|
||||
clickObj.parent().parent().remove();
|
||||
});
|
||||
|
||||
layui.apidoc.setConfigType("yml");
|
||||
layui.apidoc.setConfigPath("/static/component/laytp/config/api.config.yml?v=" + localStorage.getItem("version"));
|
||||
layui.apidoc.render();
|
||||
// 搜索菜单功能添加在这里,不要添加到js组件里面,因为会在子页面进行调用,这里只会在父页面执行
|
||||
// 这里被注释的代码,触发搜索菜单要按下回车键
|
||||
$(".search-menu").focus(
|
||||
function(){
|
||||
$(document).on("keydown",
|
||||
function(event){
|
||||
if( event.keyCode === 13 ){
|
||||
window.searchMenuData = [];
|
||||
var searchKey = $(".search-menu").val();
|
||||
if(searchKey){
|
||||
searchMenu(window.menuData, searchKey);
|
||||
layui.apidoc.setConfigType("yml");
|
||||
layui.apidoc.setConfigPath("/static/component/laytp/config/api.config.yml?v=" + localStorage.getItem("version"));
|
||||
var param = layui.apidoc.readConfig();
|
||||
param.menu.async = false;
|
||||
param.menu.data = window.searchMenuData;
|
||||
param.isSearch = true;
|
||||
layui.apidoc.render(param);
|
||||
}else{
|
||||
$("#sideMenu").show();
|
||||
$("#searchSideMenu").hide();
|
||||
renderMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
$(".search-menu").unbind();
|
||||
}
|
||||
);
|
||||
|
||||
// 递归搜索菜单
|
||||
window.searchMenu = function(menuData, searchKey){
|
||||
searchKey = searchKey.toLowerCase();
|
||||
$.each(menuData, function(i, item) {
|
||||
var oldTitle = item.title;
|
||||
item.title = item.title.toLowerCase();
|
||||
if(item.title.indexOf(searchKey) > -1){
|
||||
item.title = oldTitle;
|
||||
window.searchMenuData.push(item);
|
||||
}else{
|
||||
item.title = oldTitle;
|
||||
if(item.children && item.children.length > 0){
|
||||
searchMenu(item.children, searchKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 重新渲染菜单
|
||||
window.renderMenu = function(){
|
||||
$(".search-menu").val('');
|
||||
layui.apidoc.setConfigType("yml");
|
||||
layui.apidoc.setConfigPath("/static/component/laytp/config/api.config.yml?v=" + localStorage.getItem("version"));
|
||||
var config = layui.apidoc.readConfig();
|
||||
layui.apidoc.menuRender(config);
|
||||
};
|
||||
|
||||
window.api = {
|
||||
testInline: function (id) {
|
||||
let route = $("#test_inline_" + id).attr('route');
|
||||
let url = nowSetJson.apiUrl + route;
|
||||
let method = $("#test_inline_" + id).attr('method');
|
||||
|
||||
if (nowSetJson.createSign === "1") {
|
||||
nowSetJson.headerJson['request-time'] = Date.now();
|
||||
nowSetJson.headerJson.sign = api.createSign(nowSetJson.headerJson['request-time']);
|
||||
}
|
||||
|
||||
let formData = new FormData();
|
||||
$('#test_inline_'+id).find('input').each(function (i, input) {
|
||||
if ($(input).attr('type') == 'file') {
|
||||
formData.append($(input).attr('name'), $(input)[0].files[0]);
|
||||
} else {
|
||||
formData.append($(input).attr('name'), $(input).val())
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
headers: nowSetJson.headerJson,
|
||||
data: $('#test_inline_'+id).prop('method').toLowerCase() == 'get' ? $('#test_inline_'+id).serialize() : formData,
|
||||
type: method,
|
||||
dataType: 'json',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function (data, textStatus, xhr) {
|
||||
if (typeof data === 'object') {
|
||||
var str = JSON.stringify(data, null, 2);
|
||||
if(str) {
|
||||
$('#response_' + id).html(hljs.highlightAuto(str).value);
|
||||
}
|
||||
} else {
|
||||
$('#response_' + id).html(data || '');
|
||||
}
|
||||
$('#response_headers_' + id).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
|
||||
$('#response_card_' + id).show();
|
||||
facade.success('请求成功');
|
||||
},
|
||||
error: function (xhr) {
|
||||
try {
|
||||
var str = JSON.stringify($.parseJSON(xhr.responseText), null, 2);
|
||||
} catch (e) {
|
||||
var str = xhr.responseText;
|
||||
}
|
||||
$('#response_headers_' + id).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
|
||||
if(str){
|
||||
$('#response_' + id).html(hljs.highlightAuto(str).value);
|
||||
}
|
||||
$('#response_card_' + id).show();
|
||||
facade.error('请求失败');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
},
|
||||
createSign: function (requestTime) {
|
||||
return md5(md5(requestTime) + md5(createSignKey)).toUpperCase();
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user