Ver código fonte

更新查询豇豆

havocera 1 ano atrás
pai
commit
5a4c0adb53
71 arquivos alterados com 6849 adições e 0 exclusões
  1. 94 0
      config/apidoc.php
  2. 6 0
      public/nginx.htaccess
  3. 1 0
      vendor/hg/apidoc/.gitignore
  4. 21 0
      vendor/hg/apidoc/LICENSE
  5. 88 0
      vendor/hg/apidoc/README.md
  6. 51 0
      vendor/hg/apidoc/composer.json
  7. 192 0
      vendor/hg/apidoc/src/Auth.php
  8. 24 0
      vendor/hg/apidoc/src/ConfigProvider.php
  9. 265 0
      vendor/hg/apidoc/src/Controller.php
  10. 56 0
      vendor/hg/apidoc/src/Install.php
  11. 84 0
      vendor/hg/apidoc/src/annotation/AddField.php
  12. 59 0
      vendor/hg/apidoc/src/annotation/After.php
  13. 25 0
      vendor/hg/apidoc/src/annotation/Author.php
  14. 57 0
      vendor/hg/apidoc/src/annotation/Before.php
  15. 25 0
      vendor/hg/apidoc/src/annotation/ContentType.php
  16. 25 0
      vendor/hg/apidoc/src/annotation/Desc.php
  17. 80 0
      vendor/hg/apidoc/src/annotation/EventBase.php
  18. 25 0
      vendor/hg/apidoc/src/annotation/Field.php
  19. 24 0
      vendor/hg/apidoc/src/annotation/Group.php
  20. 50 0
      vendor/hg/apidoc/src/annotation/Header.php
  21. 35 0
      vendor/hg/apidoc/src/annotation/Md.php
  22. 25 0
      vendor/hg/apidoc/src/annotation/Method.php
  23. 62 0
      vendor/hg/apidoc/src/annotation/Param.php
  24. 88 0
      vendor/hg/apidoc/src/annotation/ParamBase.php
  25. 25 0
      vendor/hg/apidoc/src/annotation/ParamType.php
  26. 61 0
      vendor/hg/apidoc/src/annotation/Query.php
  27. 49 0
      vendor/hg/apidoc/src/annotation/ResponseError.php
  28. 35 0
      vendor/hg/apidoc/src/annotation/ResponseErrorMd.php
  29. 57 0
      vendor/hg/apidoc/src/annotation/ResponseSuccess.php
  30. 35 0
      vendor/hg/apidoc/src/annotation/ResponseSuccessMd.php
  31. 62 0
      vendor/hg/apidoc/src/annotation/Returned.php
  32. 26 0
      vendor/hg/apidoc/src/annotation/RouteMiddleware.php
  33. 51 0
      vendor/hg/apidoc/src/annotation/RouteParam.php
  34. 25 0
      vendor/hg/apidoc/src/annotation/Sort.php
  35. 25 0
      vendor/hg/apidoc/src/annotation/Tag.php
  36. 25 0
      vendor/hg/apidoc/src/annotation/Title.php
  37. 25 0
      vendor/hg/apidoc/src/annotation/Url.php
  38. 25 0
      vendor/hg/apidoc/src/annotation/WithoutField.php
  39. 94 0
      vendor/hg/apidoc/src/config.php
  40. 97 0
      vendor/hg/apidoc/src/config/plugin/hg/apidoc/app.php
  41. 3 0
      vendor/hg/apidoc/src/config/plugin/hg/apidoc/route.php
  42. 62 0
      vendor/hg/apidoc/src/exception/ErrorException.php
  43. 33 0
      vendor/hg/apidoc/src/exception/HttpException.php
  44. 354 0
      vendor/hg/apidoc/src/generator/Index.php
  45. 339 0
      vendor/hg/apidoc/src/generator/ParseTemplate.php
  46. 81 0
      vendor/hg/apidoc/src/middleware/HyperfMiddleware.php
  47. 17 0
      vendor/hg/apidoc/src/middleware/LaravelMiddleware.php
  48. 17 0
      vendor/hg/apidoc/src/middleware/ThinkPHPMiddleware.php
  49. 88 0
      vendor/hg/apidoc/src/middleware/WebmanMiddleware.php
  50. 142 0
      vendor/hg/apidoc/src/parses/ParseAnnotation.php
  51. 700 0
      vendor/hg/apidoc/src/parses/ParseApiDetail.php
  52. 315 0
      vendor/hg/apidoc/src/parses/ParseApiMenus.php
  53. 100 0
      vendor/hg/apidoc/src/parses/ParseCodeTemplate.php
  54. 139 0
      vendor/hg/apidoc/src/parses/ParseMarkdown.php
  55. 163 0
      vendor/hg/apidoc/src/parses/ParseModel.php
  56. 160 0
      vendor/hg/apidoc/src/providers/BaseService.php
  57. 56 0
      vendor/hg/apidoc/src/providers/CommonService.php
  58. 52 0
      vendor/hg/apidoc/src/providers/HyperfService.php
  59. 110 0
      vendor/hg/apidoc/src/providers/LaravelService.php
  60. 94 0
      vendor/hg/apidoc/src/providers/ThinkPHP5Service.php
  61. 107 0
      vendor/hg/apidoc/src/providers/ThinkPHPService.php
  62. 43 0
      vendor/hg/apidoc/src/providers/WebmanService.php
  63. 36 0
      vendor/hg/apidoc/src/utils/AbstractAnnotation.php
  64. 22 0
      vendor/hg/apidoc/src/utils/ApiCrossDomain.php
  65. 153 0
      vendor/hg/apidoc/src/utils/AutoRegisterRouts.php
  66. 298 0
      vendor/hg/apidoc/src/utils/Cache.php
  67. 95 0
      vendor/hg/apidoc/src/utils/ConfigProvider.php
  68. 238 0
      vendor/hg/apidoc/src/utils/DirAndFile.php
  69. 548 0
      vendor/hg/apidoc/src/utils/Helper.php
  70. 59 0
      vendor/hg/apidoc/src/utils/Lang.php
  71. 46 0
      vendor/hg/apidoc/src/utils/Request.php

+ 94 - 0
config/apidoc.php

@@ -0,0 +1,94 @@
+<?php
+return [
+    // (选配)文档标题,显示在左上角与首页
+    'title'              => 'Apidoc',
+    // (选配)文档描述,显示在首页
+    'desc'               => '',
+    // (必须)设置文档的应用/版本
+    'apps'           => [
+        [
+            // (必须)标题
+            'title'=>'Api接口',
+            // (必须)控制器目录地址
+            'path'=>'app\controller',
+            // (必须)唯一的key
+            'key'=>'dakkdakdkasdkmmmm',
+        ]
+    ],
+    // (必须)指定通用注释定义的文件地址
+    'definitions'        => "app\common\controller\Definitions",
+    // (必须)自动生成url规则,当接口不添加@Apidoc\Url ("xxx")注解时,使用以下规则自动生成
+    'auto_url' => [
+        // 字母规则,lcfirst=首字母小写;ucfirst=首字母大写;
+        'letter_rule' => "lcfirst",
+        // url前缀
+        'prefix'=>"",
+    ],
+    // 是否自动注册路由
+    'auto_register_routes'=>false,
+    // (必须)缓存配置
+    'cache'              => [
+        // 是否开启缓存
+        'enable' => false,
+    ],
+    // (必须)权限认证配置
+    'auth'               => [
+        // 是否启用密码验证
+        'enable'     => false,
+        // 全局访问密码
+        'password'   => "123456",
+        // 密码加密盐
+        'secret_key' => "apidoc#hg_code",
+        // 授权访问后的有效期
+        'expire' => 24*60*60
+    ],
+    // 全局参数
+    'params'=>[
+        // (选配)全局的请求Header
+        'header'=>[
+            // name=字段名,type=字段类型,require=是否必须,default=默认值,desc=字段描述
+            ['name'=>'Authorization','type'=>'string','require'=>true,'desc'=>'身份令牌Token'],
+        ],
+        // (选配)全局的请求Query
+        'query'=>[
+            // 同上 header
+        ],
+        // (选配)全局的请求Body
+        'body'=>[
+            // 同上 header
+        ],
+    ],
+    // 全局响应体
+    'responses'=>[
+        // 成功响应体
+        'success'=>[
+            ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1],
+            ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
+            //参数同上 headers;main=true来指定接口Returned参数挂载节点
+            ['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object','require'=>1],
+        ],
+        // 异常响应体
+        'error'=>[
+            ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1,'md'=>'/docs/HttpError.md'],
+            ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
+        ]
+    ],
+    //(选配)默认作者
+    'default_author'=>'',
+    //(选配)默认请求类型
+    'default_method'=>'GET',
+    //(选配)允许跨域访问
+    'allowCrossDomain'=>false,
+    /**
+     * (选配)解析时忽略带@注解的关键词,当注解中存在带@字符并且非Apidoc注解,如 @key test,此时Apidoc页面报类似以下错误时:
+     * [Semantical Error] The annotation "@key" in method xxx() was never imported. Did you maybe forget to add a "use" statement for this annotation?
+     */
+    'ignored_annitation'=>[],
+
+    // (选配)数据库配置
+    'database'=>[],
+    // (选配)Markdown文档
+    'docs'              => [],
+    // (选配)代码生成器配置 注意:是一个二维数组
+    'generator' =>[]
+];

+ 6 - 0
public/nginx.htaccess

@@ -0,0 +1,6 @@
+location / {
+    if (!-e $request_filename){
+    rewrite ^(.*)$ /index.php?s=$1 last; 
+    break;
+    }
+}

+ 1 - 0
vendor/hg/apidoc/.gitignore

@@ -0,0 +1 @@
+.idea

+ 21 - 0
vendor/hg/apidoc/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 HG
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 88 - 0
vendor/hg/apidoc/README.md

@@ -0,0 +1,88 @@
+<div align="center">
+    <img width="160"  src="https://docs.apidoc.icu/logo.png">
+</div>
+
+<h1 align="center" style="margin-top: 0;padding-top: 0;">
+  Apidoc
+</h1>
+
+<div align="center">
+ 基于PHP的注解生成API文档及Api接口开发工具
+</div>
+
+<div align="center" style="margin-top:10px;margin-bottom:50px;">
+<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/v/hg/apidoc"></a>
+<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/dt/hg/apidoc"></a>
+<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/dm/hg/apidoc"></a>
+<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/dd/hg/apidoc"></a>
+<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/l/hg/apidoc"></a>
+<a href="https://github.com/HGthecode/apidoc-php"><img src="https://img.shields.io/github/issues/HGthecode/apidoc-php"></a>
+<a href="https://github.com/HGthecode/apidoc-php"><img src="https://img.shields.io/github/forks/HGthecode/apidoc-php"></a>
+
+</div>
+
+
+## 🤷‍♀️ Apidoc是什么?
+
+Apidoc是一个通过解析注解生成Api接口文档的PHP composer扩展,兼容Laravel、ThinkPHP、Hyperf、Webman等框架;
+全面的注解引用、数据表字段引用,简单的注解即可生成Api文档,而Apidoc不仅于接口文档,在线接口调试、Mock调试数据、调试事件处理、Json/TypeScript生成、接口生成器、代码生成器等诸多实用功能,致力于提高Api接口开发效率。
+
+
+## ✨特性
+
+- 开箱即用:无繁杂的配置、安装后按文档编写注释即可自动生成API文档。
+- 轻松编写:支持通用注释引用、业务逻辑层、数据表字段的引用,几句注释即可完成。
+- 在线调试:在线文档可直接调试,并支持全局请求/Mock参数/事件处理,接口调试省时省力。
+- 安全高效:支持访问密码验证、应用/版本独立密码;支持文档缓存。
+- 多应用/多版本:可适应各种单应用、多应用、多版本的项目的Api管理。
+- 分组/Tag:可对控制器/接口进行多级分组或定义Tag。
+- Markdown文档:支持.md文件的文档展示。
+- Json/TypeScript生成:文档自动生成接口的Json及TypeScript。
+- 代码生成器:配置+模板即可快速生成代码及数据表的创建,大大提高工作效率。
+
+
+## 📌兼容
+
+以下框架已内置兼容,可开箱即用
+
+|框架|版本|说明|
+|-|-|-|
+|ThinkPHP|5.1、6.x||
+|Laravel|8.x、9.x、10.x||
+|Webman|1.x||
+|Hyperf|2.x、3.x||
+
+
+## 📖使用文档
+
+[https://docs.apidoc.icu](https://docs.apidoc.icu/)
+
+[https://hg-code.gitee.io/apidoc-php/](https://hg-code.gitee.io/apidoc-php/)
+
+## 🏆支持我们
+
+如果本项目对您有所帮助,请点个Star支持我们
+
+- [Github](https://github.com/HGthecode/apidoc-php) -> <a href="https://github.com/HGthecode/apidoc-php" target="_blank">
+  <img height="22" src="https://img.shields.io/github/stars/HGthecode/apidoc-php?style=social" class="attachment-full size-full" alt="Star me on GitHub" data-recalc-dims="1" /></a>
+
+- [Gitee](https://gitee.com/hg-code/apidoc-php) -> <a href="https://gitee.com/hg-code/apidoc-php/stargazers"><img src="https://gitee.com/hg-code/apidoc-php/badge/star.svg" alt="star"></a>
+
+
+## 🌐交流群
+
+![QQ群](https://docs.apidoc.icu/qq-qun.png)
+
+
+
+## 💡鸣谢
+
+<a href="https://github.com/doctrine/annotations" target="_blank">doctrine/annotations</a>
+
+
+## 🔗链接
+ <a href="https://github.com/HGthecode/apidoc-ui" target="_blank">ApiDoc UI</a>
+ 
+ <a href="https://github.com/HGthecode/apidoc-demos" target="_blank">ApiDoc Demo</a>
+
+

+ 51 - 0
vendor/hg/apidoc/composer.json

@@ -0,0 +1,51 @@
+{
+    "name": "hg/apidoc",
+    "description": "根据注解生成API文档,兼容Laravel、ThinkPHP、Hyperf、Webman等框架;在线调试、Markdown文档、多应用/多版本、Mock数据、授权访问、接口生成器、代码生成器等众多实用功能",
+    "keywords": [
+        "apidoc",
+        "api文档",
+        "接口文档",
+        "自动生成api",
+        "注释生成",
+        "php接口文档",
+        "api文档",
+        "Markdown",
+        "注解"
+      ],
+    "require": {
+        "php": "^7.1 || ^8.0",
+        "doctrine/annotations": "^1 || ^2",
+        "doctrine/lexer": "^1 || ^2"
+    },
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "hg-code",
+            "email": "376401263@qq.com"
+        }
+    ],
+    "autoload": {
+        "psr-4": {
+            "hg\\apidoc\\": "src/"
+        }
+    },
+    "extra": {
+        "laravel": {
+            "providers": [
+                "hg\\apidoc\\providers\\LaravelService"
+            ]
+        },
+        "think": {
+            "services": [
+                "hg\\apidoc\\providers\\ThinkPHPService"
+            ],
+            "config": {
+                "apidoc": "src/config.php"
+            }
+        },
+        "hyperf": {
+            "config": "hg\\apidoc\\ConfigProvider"
+        }
+    },
+    "minimum-stability": "dev"
+}

+ 192 - 0
vendor/hg/apidoc/src/Auth.php

@@ -0,0 +1,192 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc;
+
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\Helper;
+
+class Auth
+{
+    protected $authConfig = [];
+
+    public function __construct($config)
+    {
+        $authConfig = !empty($config['auth'])?$config['auth']:[];
+        if (empty($authConfig['secret_key'])){
+            $authConfig['secret_key'] = "apidoc#hgcode";
+        }
+        if (empty($authConfig['expire'])){
+            $authConfig['expire'] = 86400;
+        }
+        $this->authConfig = $authConfig;
+    }
+
+    /**
+     * 验证密码是否正确
+     * @param $password
+     * @return false|string
+     */
+    public function verifyAuth(string $password, string $appKey)
+    {
+        $authConfig = $this->authConfig;
+        if (!empty($appKey)) {
+            $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+            $currentApp  = $currentAppConfig['appConfig'];
+            if (!empty($currentApp) && !empty($currentApp['password'])) {
+                // 应用密码
+                if (md5($currentApp['password']) === $password) {
+                    return $this->createToken($currentApp['password'],$authConfig['expire']);
+                }
+                throw new ErrorException("password error");
+            }
+        }
+        if ($authConfig['enable']) {
+            // 密码验证
+            if (md5($authConfig['password']) === $password) {
+                return $this->createToken($authConfig['password'],$authConfig['expire']);
+            }
+            throw new ErrorException("password error");
+        }
+        return false;
+    }
+
+    /**
+     * 验证token是否可用
+     * @param $request
+     * @return bool
+     */
+    public function checkAuth($params=[]): bool
+    {
+        $authConfig = $this->authConfig;
+        $token = !empty($params['token'])?$params['token']:"";
+        $appKey = !empty($params['appKey'])?$params['appKey']:"";
+        if (!empty($appKey)) {
+            $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+            $currentApp  = $currentAppConfig['appConfig'];
+            if (!empty($currentApp) && !empty($currentApp['password'])) {
+                if (empty($token)) {
+                    throw new ErrorException("token not found");
+                }
+                // 应用密码
+                if ($this->checkToken($token, $currentApp['password'])) {
+                    return true;
+                } else {
+                    throw new ErrorException("token error");
+                }
+            } else if (empty($authConfig['enable'])) {
+                return true;
+            }
+        }
+        if($authConfig['enable'] && empty($token)){
+            throw new ErrorException("token not found");
+        }else if (!empty($token) && !$this->checkToken($token, "")  && $authConfig['enable']) {
+            throw new ErrorException("token error");
+        }
+        return true;
+    }
+
+
+    /**
+     * 获取tokencode
+     * @param string $password
+     * @return string
+     */
+    protected static function getTokenCode(string $password): string
+    {
+        return md5(md5($password));
+    }
+
+
+    /**
+     * 创建token
+     * @param string $password
+     * @return string
+     */
+    protected function createToken(string $password): string
+    {
+        $authConfig = $this->authConfig;
+        $data = [
+            'key'=>static::getTokenCode($password),
+            'expire'=>time()+$authConfig['expire']
+        ];
+        $code = json_encode($data);
+        return static::handleToken($code, "CE",$authConfig['secret_key']);
+    }
+
+    /**
+     * 验证token是否可用
+     * @param $token
+     * @return bool
+     */
+    protected function checkToken(string $token, string $password): bool
+    {
+        $authConfig = $this->authConfig;
+        if (empty($password)){
+            $password = $authConfig['password'];
+        }
+        $decode = static::handleToken($token, "DE",$authConfig['secret_key']);
+        $deData = json_decode($decode,true);
+
+        if (!empty($deData['key']) && $deData['key'] === static::getTokenCode($password) && !empty($deData['expire']) && $deData['expire']>time()){
+            return true;
+        }
+
+        return false;
+    }
+
+
+
+    /**
+     * 处理token
+     * @param $string
+     * @param string $operation
+     * @param string $key
+     * @param int $expiry
+     * @return false|string
+     */
+    protected static function handleToken(string $string, string $operation = 'DE', string $key = '', int $expiry = 0):string
+    {
+        $ckey_length   = 4;
+        $key           = md5($key);
+        $keya          = md5(substr($key, 0, 16));
+        $keyb          = md5(substr($key, 16, 16));
+        $keyc          = $ckey_length ? ($operation == 'DE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
+        $cryptkey      = $keya . md5($keya . $keyc);
+        $key_length    = strlen($cryptkey);
+        $string        = $operation == 'DE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
+        $string_length = strlen($string);
+        $result        = '';
+        $box           = range(0, 255);
+        $rndkey        = array();
+        for ($i = 0; $i <= 255; $i++) {
+            $rndkey[$i] = ord($cryptkey[$i % $key_length]);
+        }
+        for ($j = $i = 0; $i < 256; $i++) {
+            $j       = ($j + $box[$i] + $rndkey[$i]) % 256;
+            $tmp     = $box[$i];
+            $box[$i] = $box[$j];
+            $box[$j] = $tmp;
+        }
+        for ($a = $j = $i = 0; $i < $string_length; $i++) {
+            $a       = ($a + 1) % 256;
+            $j       = ($j + $box[$a]) % 256;
+            $tmp     = $box[$a];
+            $box[$a] = $box[$j];
+            $box[$j] = $tmp;
+            $result  .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
+        }
+        if ($operation == 'DE') {
+            $subNumber = (int)substr($result, 0, 10);
+            if (($subNumber == 0 || $subNumber - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
+                return substr($result, 26);
+            } else {
+                return '';
+            }
+        } else {
+            return $keyc . str_replace('=', '', base64_encode($result));
+        }
+    }
+
+}

+ 24 - 0
vendor/hg/apidoc/src/ConfigProvider.php

@@ -0,0 +1,24 @@
+<?php
+declare(strict_types=1);
+/**
+ * Hyperf the Config Provider
+ */
+namespace hg\apidoc;
+
+class ConfigProvider
+{
+    public function __invoke(): array
+    {
+        return [
+            'dependencies' => [],
+            'publish' => [
+                [
+                    'id' => 'config',
+                    'description' => 'The config of apidoc.',
+                    'source' => __DIR__ . '/config.php',
+                    'destination' => BASE_PATH . '/config/autoload/apidoc.php',
+                ],
+            ],
+        ];
+    }
+}

+ 265 - 0
vendor/hg/apidoc/src/Controller.php

@@ -0,0 +1,265 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc;
+
+use hg\apidoc\parses\ParseApiDetail;
+use hg\apidoc\parses\ParseCodeTemplate;
+use hg\apidoc\parses\ParseApiMenus;
+use hg\apidoc\parses\ParseMarkdown;
+use hg\apidoc\utils\Cache;
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+use hg\apidoc\utils\Request;
+use hg\apidoc\exception\ErrorException;
+
+
+class Controller
+{
+
+    protected $config;
+
+    protected $requestParams=[];
+
+    protected $lang="";
+
+
+    public function init($checkAuth=false){
+        $this->config = ConfigProvider::get();
+        if (isset($this->config['enable']) && $this->config['enable']===false){
+            throw new ErrorException("apidoc close");
+        }
+        if (!empty($this->config['request_params'])){
+            $this->requestParams = $this->config['request_params'];
+        }else{
+            $this->requestParams = (new Request())->param();
+        }
+        if (!empty($this->requestParams['lang']) && !empty($this->config['lang_register_function'])){
+            $this->lang = $this->requestParams['lang'];
+            $this->config['lang_register_function']($this->lang);
+        }
+        if($checkAuth){
+            (new Auth($this->config))->checkAuth($this->requestParams);
+        }
+    }
+
+
+
+    /**
+     * 获取配置
+     * @return \think\response\Json
+     */
+    public function getConfig(){
+        $this->init(true);
+        $config = ConfigProvider::getFeConfig();
+        return Helper::showJson(0,"",$config);
+    }
+
+    /**
+     * 验证密码
+     */
+    public function verifyAuth(){
+        $this->init();
+        $config = $this->config;
+        $params = $this->requestParams;
+        if (empty($params['password'])){
+            throw new ErrorException( "password not found");
+        }
+        $appKey = !empty($params['appKey'])?$params['appKey']:"";
+
+        if (!$appKey && !(!empty($config['auth']) && $config['auth']['enable'])) {
+            return false;
+        }
+        $hasAuth = (new Auth($config))->verifyAuth($params['password'],$appKey);
+        $res = [
+            "token"=>$hasAuth
+        ];
+        return Helper::showJson(0,"",$res);
+
+
+    }
+
+    /**
+     * 获取api文档菜单
+     */
+    public function getApiMenus(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+
+        if (!empty($params['appKey'])){
+            $appKey = $params['appKey'];
+        }
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $currentApp  = $currentAppConfig['appConfig'];
+
+        if (!empty($config['cache']) && $config['cache']['enable']){
+            $cacheKey = Helper::getCacheKey('apiMenu',$appKey,$this->lang);
+            $cacheData = (new Cache())->get($cacheKey);
+            if ($cacheData && empty($params['reload'])){
+                $apiData = $cacheData;
+            }else{
+                // 生成数据并缓存
+                $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey);
+                (new Cache())->set($cacheKey,$apiData);
+            }
+        }else{
+            // 生成数据
+            $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey);
+        }
+
+        $groups=!empty($currentApp['groups'])?$currentApp['groups']:[];
+        $json=[
+            'data'=>$apiData['data'],
+            'app'=>$currentApp,
+            'groups'=>$groups,
+            'tags'=>$apiData['tags'],
+        ];
+
+        return Helper::showJson(0,"",$json);
+    }
+
+
+    public function getApiDetail(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        if (empty($params['path'])){
+            throw new ErrorException("path not found");
+        }
+        $appKey = !empty($params['appKey'])?$params['appKey']:"";
+        $apiKey = urldecode($params['path']);
+
+        if (!empty($config['cache']) && $config['cache']['enable']){
+            $cacheKey = Helper::getCacheKey('apiDetail',$appKey,$this->lang,$params['path']);
+            $cacheData = (new Cache())->get($cacheKey);
+            if ($cacheData && empty($params['reload'])){
+                $res = $cacheData;
+            }else{
+                // 生成数据并缓存
+                $res = (new ParseApiDetail($config))->renderApiDetail($appKey,$apiKey);
+                (new Cache())->set($cacheKey,$res);
+            }
+        }else{
+            // 生成数据
+            $res = (new ParseApiDetail($config))->renderApiDetail($appKey,$apiKey);
+        }
+        $res['appKey']=$appKey;
+        return Helper::showJson(0,"",$res);
+    }
+
+
+    public function getMdMenus(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        $appKey = "";
+        if (!empty($params['appKey'])){
+            // 获取指定应用
+            $appKey = $params['appKey'];
+        }
+        $docs = (new ParseMarkdown($config))->getDocsMenu($appKey,$this->lang);
+        return Helper::showJson(0,"",$docs);
+
+    }
+
+    /**
+     * 获取md文档内容
+     * @return \think\response\Json
+     */
+    public function getMdDetail(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        try {
+            if (empty($params['path'])){
+                throw new ErrorException("mdPath not found");
+            }
+            if (empty($params['appKey'])){
+                throw new ErrorException("appkey not found");
+            }
+
+            $path = urldecode($params['path']);
+            $content = (new ParseMarkdown($config))->getContent($params['appKey'],$path,$this->lang);
+            $res = [
+                'content'=>$content,
+            ];
+            return Helper::showJson(0,"",$res);
+
+        } catch (ErrorException $e) {
+            return Helper::showJson($e->getCode(),$e->getMessage());
+        }
+    }
+
+
+    public function createGenerator(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        $res = (new generator\Index($config))->create($params);
+        return Helper::showJson(0,"",$res);
+    }
+
+    public function cancelAllCache(){
+        $this->init(true);
+        $config = $this->config;
+        $path = APIDOC_STORAGE_PATH . $config['cache']['folder'];
+        $res = DirAndFile::deleteDir($path);
+        return Helper::showJson(0,"",$path);
+    }
+
+    public function createAllCache(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        $apps = Helper::getAllApps($config['apps']);
+        $cache = new Cache();
+        DirAndFile::deleteDir(APIDOC_STORAGE_PATH . $config['cache']['folder'].'/'.'apis');
+        if (!empty($apps) && count($apps)){
+            try {
+                foreach ($apps as $app) {
+                    // 缓存菜单
+                    $appKey = $app['appKey'];
+                    $controllerData = (new ParseApiMenus($config))->renderApiMenus($appKey);
+                    if (!empty($controllerData['data']) && count($controllerData['data'])){
+                        foreach ($controllerData['data'] as $controller) {
+                            if (!empty($controller['children']) && count($controller['children'])){
+                                foreach ($controller['children'] as $item) {
+                                    if (!empty($item['url']) && !empty($item['menuKey'])){
+                                        $apiDetail = (new ParseApiDetail($config))->renderApiDetail($appKey,urldecode($item['menuKey']));
+                                        $apiDetailCacheKey = Helper::getCacheKey('apiDetail',$appKey,$this->lang,$item['menuKey']);
+                                        $cache->set($apiDetailCacheKey,$apiDetail);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    $cacheKey = Helper::getCacheKey('apiMenu',$appKey,$this->lang);
+                    $cache->set($cacheKey,$controllerData);
+                }
+            } catch (\ReflectionException $e) {
+                DirAndFile::deleteDir(APIDOC_STORAGE_PATH . $config['cache']['folder'].'/'.'apis');
+                throw new ErrorException($e->getMessage());
+            }
+        }
+
+        return Helper::showJson(0,"",true);
+    }
+
+    public function renderCodeTemplate(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+
+        $code = (new ParseCodeTemplate($config))->renderCode($params);
+
+        return Helper::showJson(0,"",[
+            'code'=>$code
+        ]);
+
+    }
+
+
+}

+ 56 - 0
vendor/hg/apidoc/src/Install.php

@@ -0,0 +1,56 @@
+<?php
+namespace hg\apidoc;
+/**
+ * Webman Install
+ */
+class Install
+{
+    const WEBMAN_PLUGIN = true;
+
+    /**
+     * @var array
+     */
+    protected static $configPath = array (
+      'config/plugin/hg/apidoc' => 'config/plugin/hg/apidoc',
+    );
+
+    /**
+     * Install
+     * @return void
+     */
+    public static function install()
+    {
+        foreach (static::$configPath as $source => $dest) {
+            if ($pos = strrpos($dest, '/')) {
+                $parent_dir = base_path() . '/' . substr($dest, 0, $pos);
+                if (!is_dir($parent_dir)) {
+                    mkdir($parent_dir, 0777, true);
+                }
+            }
+            //symlink(__DIR__ . "/$source", base_path()."/$dest");
+            copy_dir(__DIR__ . "/$source", base_path() . "/$dest");
+            echo "Create $dest";
+        }
+    }
+
+    /**
+     * Uninstall
+     * @return void
+     */
+    public static function uninstall()
+    {
+        foreach (static::$configPath as $source => $dest) {
+            $path = base_path()."/$dest";
+            if (!is_dir($path) && !is_file($path)) {
+                continue;
+            }
+            echo "Remove $dest";
+            if (is_file($path) || is_link($path)) {
+                unlink($path);
+                continue;
+            }
+            remove_dir($path);
+        }
+    }
+
+}

+ 84 - 0
vendor/hg/apidoc/src/annotation/AddField.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 添加模型的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class AddField extends ParamBase
+{
+    /**
+     * 字段名
+     * @var string
+     */
+    public $name;
+    /**
+     * 类型
+     * @var string
+     */
+    public $type = 'string';
+
+
+    /**
+     * 默认值
+     * @var string
+     */
+    public $default;
+
+    /**
+     * 描述
+     * @var string
+     */
+    public $desc;
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+
+    /**
+     * 说明md内容
+     * @var string
+     */
+    public $md;
+
+
+    /**
+     * @param string $name 字段名
+     * @param string $type 字段类型
+     * @param string $desc 字段名称
+     * @param bool $require 是否必须
+     * @param string $ref 引用注解/模型
+     * @param string $table 引用数据表
+     * @param string $md Md文本内容
+     * @param string $field 指定Ref引入的字段
+     * @param string $withoutField 排除Ref引入的字段
+     * @param string $mock Mock规则
+     * @param string $children 字段子节点
+     */
+    public function __construct(
+        $name = '',
+        string $type = '',
+        string $desc = '',
+        bool $require = false,
+        $ref = "",
+        $table = "",
+        string $md = "",
+        $field = "",
+        $withoutField = "",
+        string $mock = "",
+        ...$attrs
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+}

+ 59 - 0
vendor/hg/apidoc/src/annotation/After.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 接口调试前置事件
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+final class After extends EventBase
+{
+
+    /**
+     * 事件
+     * @Enum({"setGlobalHeader", "setGlobalQuery", "setGlobalBody","clearGlobalHeader","clearGlobalQuery","clearGlobalBody","ajax"})
+     * @var string
+     */
+    public $event;
+
+
+    /**
+     * @param string $event 事件名
+     * @param string $name 事件名称
+     * @param string $appKey 设置全局参数setGlobalHeader、setGlobalParam时指定应用
+     * @param string $key 字段名
+     * @param string $value 字段值
+     * @param string|array $ref 事件引用
+     * @param string $url ajax事件时的url
+     * @param string $method ajax事件时的Method
+     * @param string $contentType ajax事件时的 content-type
+     * @param string $desc 事件描述
+     * @param array $before 执行之前的事件
+     * @param array $after 执行之后的事件
+     */
+    public function __construct(
+        $event = '',
+        string $name = '',
+        string $appKey = "",
+        string $key = '',
+        string $value = '',
+               $ref = "",
+        string $url = '',
+        string $method = '',
+        string $contentType = "",
+        string $desc = "",
+        array  $before = [],
+        array  $after = []
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+
+
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/Author.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 作者
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Author extends AbstractAnnotation
+{
+    /**
+     * @param string $value 作者名称
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 57 - 0
vendor/hg/apidoc/src/annotation/Before.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 接口调试前置事件
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+final class Before extends EventBase
+{
+    /**
+     * 事件
+     * @Enum({"setHeader","setQuery","setBody", "clearHeader", "clearQuery", "clearBody", "setGlobalHeader", "setGlobalQuery", "setGlobalBody","clearGlobalHeader","clearGlobalQuery","clearGlobalBody","ajax"})
+     * @var string
+     */
+    public $event;
+
+
+    /**
+     * @param string $event 事件名
+     * @param string $name 事件名称
+     * @param string $appKey 设置全局参数setGlobalHeader、setGlobalParam时指定应用
+     * @param string $key 字段名
+     * @param string $value 字段值
+     * @param string|array $ref 事件引用
+     * @param string $url ajax事件时的url
+     * @param string $method ajax事件时的Method
+     * @param string $contentType ajax事件时的 content-type
+     * @param string $desc 事件描述
+     * @param array $before 执行之前的事件
+     * @param array $after 执行之后的事件
+     */
+    public function __construct(
+        $event = '',
+        string $name = '',
+        string $appKey = "",
+        string $key = '',
+        string $value = '',
+               $ref = "",
+        string $url = '',
+        string $method = '',
+        string $contentType = "",
+        string $desc = "",
+        array  $before = [],
+        array  $after = []
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/ContentType.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 调试时请求类型
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class ContentType extends AbstractAnnotation
+{
+    /**
+     * @param string $value 调试时请求类型
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/Desc.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 描述
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","CLASS"})
+ */
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Desc extends AbstractAnnotation
+{
+    /**
+     * @param string $value 描述
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 80 - 0
vendor/hg/apidoc/src/annotation/EventBase.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use hg\apidoc\utils\AbstractAnnotation;
+
+abstract class EventBase extends AbstractAnnotation
+{
+
+    /**
+     * 名称
+     * @var string
+     */
+    public $name;
+
+    /**
+     * key
+     * @var string
+     */
+    public $key;
+
+    /**
+     * 事件处理的值
+     * @var string
+     */
+    public $value;
+
+
+    /**
+     * ajax时的url
+     * @var string
+     */
+    public $url;
+
+    /**
+     * ajax时的Method
+     * @Enum({"GET", "POST", "PUT", "DELETE"})
+     * @var string
+     */
+    public $method;
+
+    /**
+     * ajax时的 content-type
+     * @var string
+     */
+    public $contentType;
+
+    /**
+     * 字段描述
+     * @var string
+     */
+    public $desc;
+
+    /**
+     * 引用
+     * @var string|array
+     */
+    public $ref;
+
+    /**
+     * 设置全局参数setGlobalHeader、setGlobalParam时指定应用
+     * @var string
+     */
+    public $appKey;
+
+
+    /**
+     * 执行之前的事件
+     * @var array
+     */
+    public $before;
+
+    /**
+     * 执行之后的事件
+     * @var array
+     */
+    public $after;
+
+
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/Field.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 指定Ref的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Field extends AbstractAnnotation
+{
+    /**
+     * @param string|array $value 指定Ref的字段,逗号分割
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 24 - 0
vendor/hg/apidoc/src/annotation/Group.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 分组
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"CLASS"})
+ */
+#[Attribute(Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
+class Group extends AbstractAnnotation
+{
+    /**
+     * @param string $name 分组
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 50 - 0
vendor/hg/apidoc/src/annotation/Header.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+
+/**
+ * 请求头
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Header extends ParamBase
+{
+
+
+    /**
+     * mock
+     * @var string
+     */
+    public $mock;
+
+    /**
+     * @param string $value 字段名
+     * @param string $type 字段类型
+     * @param string $desc 字段名称
+     * @param bool $require 是否必须
+     * @param string|array $ref 引用注解/模型
+     * @param string $md Md文本内容
+     * @param string|array $field 指定Ref引入的字段
+     * @param string|array $withoutField 排除Ref引入的字段
+     * @param string $mock Mock规则
+     */
+    public function __construct(
+        $value = '',
+        string $type = '',
+        bool   $require = false,
+               $ref = "",
+        string $desc = '',
+        string $md = "",
+               $field = "",
+               $withoutField = "",
+        string $mock = ""
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+
+}

+ 35 - 0
vendor/hg/apidoc/src/annotation/Md.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * Markdown
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Md extends AbstractAnnotation
+{
+    /**
+     * 引入md内容
+     * @var string|array
+     */
+    public $ref;
+
+    /**
+     * @param string $name Markdown文档内容
+     * @param string $ref 引入md文件路径
+     */
+    public function __construct(
+        $name = '',
+        string $ref = ''
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/Method.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 请求类型
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Method extends AbstractAnnotation
+{
+    /**
+     * @param string $value 请求类型
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 62 - 0
vendor/hg/apidoc/src/annotation/Param.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+
+/**
+ * 请求参数
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Param extends ParamBase
+{
+
+    /**
+     * mock
+     * @var string
+     */
+    public $mock;
+
+    /**
+     * @param string $name 字段名
+     * @param string $type 字段类型
+     * @param bool $require 是否必须
+     * @param string|int|bool $default 默认值
+     * @param string|array $ref 引用注解/模型
+     * @param string $table 引用数据表
+     * @param string|array $field 指定Ref引入的字段
+     * @param string|array $withoutField 排除Ref引入的字段
+     * @param string $desc 字段名称
+     * @param string $md Md文本内容
+     * @param string $mock Mock规则
+     * @param array $children 子参数
+     * @param string $childrenField 为tree类型时指定children字段
+     * @param string $childrenDesc 为tree类型时指定children字段说明
+     * @param string $childrenType 为array类型时指定子节点类型
+     */
+    public function __construct(
+        $name = '',
+        string $type = '',
+        bool $require = false,
+        $default = "",
+        $ref = "",
+        $table = "",
+        $field = "",
+        $withoutField = "",
+        string $desc = '',
+        string $md = "",
+        string $mock = "",
+        array $children = [],
+        string $childrenField = "",
+        string $childrenDesc = "children",
+        string $childrenType = "",
+        ...$attrs
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+
+
+}

+ 88 - 0
vendor/hg/apidoc/src/annotation/ParamBase.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+abstract class ParamBase extends AbstractAnnotation
+{
+
+    /**
+     * 类型
+     * @Enum({"string", "integer", "int", "boolean", "array", "double", "object", "tree", "file","float","date","time","datetime"})
+     * @var string
+     */
+    public $type;
+
+
+    /**
+     * 默认值
+     * @var string
+     */
+    public $default;
+
+    /**
+     * 描述
+     * @var string
+     */
+    public $desc;
+
+    /**
+     * 为tree类型时指定children字段
+     * @var string
+     */
+    public $childrenField = '';
+
+    /**
+     * 为tree类型时指定children字段说明
+     * @var string
+     */
+    public $childrenDesc = 'children';
+
+    /**
+     * 为array类型时指定子节点类型
+     * @Enum({"string", "int", "boolean", "array", "object"})
+     * @var string
+     */
+    public $childrenType = '';
+
+    /**
+     * 指定引入的字段
+     * @var string
+     */
+    public $field;
+
+    /**
+     * 指定从引入中过滤的字段
+     * @var string
+     */
+    public $withoutField;
+
+    /**
+     * 说明md内容
+     * @var string
+     */
+    public $md;
+
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+
+    /**
+     * 引入
+     * @var string|array
+     */
+    public $ref;
+
+    /**
+     * 子参数
+     * @var array
+     */
+    public $children;
+
+
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/ParamType.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 参数类型
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class ParamType extends AbstractAnnotation
+{
+    /**
+     * @param string $value 参数类型,formdata
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 61 - 0
vendor/hg/apidoc/src/annotation/Query.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+
+/**
+ * Query参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+final class Query extends ParamBase
+{
+
+    /**
+     * mock
+     * @var string
+     */
+    public $mock;
+
+    /**
+     * @param string $name 字段名
+     * @param string $type 字段类型
+     * @param bool $require 是否必须
+     * @param string|int|bool $default 默认值
+     * @param string|array $ref 引用注解/模型
+     * @param string $table 引用数据表
+     * @param string|array $field 指定Ref引入的字段
+     * @param string|array $withoutField 排除Ref引入的字段
+     * @param string $desc 字段名称
+     * @param string $md Md文本内容
+     * @param string $mock Mock规则
+     * @param array $children 子参数
+     * @param string $childrenField 为tree类型时指定children字段
+     * @param string $childrenDesc 为tree类型时指定children字段说明
+     * @param string $childrenType 为array类型时指定子节点类型
+     */
+    public function __construct(
+        $name = '',
+        string $type = '',
+        bool $require = false,
+        $default = "",
+        $ref = "",
+        $table = "",
+        $field = "",
+        $withoutField = "",
+        string $desc = '',
+        string $md = "",
+        string $mock = "",
+        array $children = [],
+        string $childrenField = "",
+        string $childrenDesc = "children",
+        string $childrenType = "",
+        ...$attrs
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+}

+ 49 - 0
vendor/hg/apidoc/src/annotation/ResponseError.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 异常响应体
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+final class ResponseError extends ParamBase
+{
+
+    /**
+     * @param string $value 字段名
+     * @param string $type 字段类型
+     * @param string $desc 字段名称
+     * @param bool $require 是否必须
+     * @param string|array $ref 引用注解/模型
+     * @param string $md Md文本内容
+     * @param string $childrenField 为tree类型时指定children字段
+     * @param string $childrenDesc 为tree类型时指定children字段说明
+     * @param string $childrenType 为array类型时指定子节点类型
+     * @param string|array $field 指定Ref引入的字段
+     * @param string|array $withoutField 排除Ref引入的字段
+     */
+    public function __construct(
+        $value = '',
+        string $type = '',
+        string $desc = '',
+        bool $require = false,
+        $ref = "",
+        string $md = "",
+        string $childrenField = "",
+        string $childrenDesc = "children",
+        string $childrenType = "",
+        $field = "",
+        $withoutField = "",
+        string $mock = ""
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+
+}

+ 35 - 0
vendor/hg/apidoc/src/annotation/ResponseErrorMd.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 异常响应体的Markdown内容
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class ResponseErrorMd extends AbstractAnnotation
+{
+    /**
+     * 引入md文件
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * @param string $name Markdown文档内容
+     * @param string $ref 引入md文件路径
+     */
+    public function __construct(
+        $name = '',
+        string $ref = ''
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+}

+ 57 - 0
vendor/hg/apidoc/src/annotation/ResponseSuccess.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 成功响应体
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+final class ResponseSuccess extends ParamBase
+{
+
+    /**
+     * 数据挂载节点
+     * @var boolean
+     */
+    public $main;
+
+    /**
+     * @param string $value 字段名
+     * @param string $type 字段类型
+     * @param string $desc 字段名称
+     * @param bool $require 是否必须
+     * @param string|array $ref 引用注解/模型
+     * @param string $md Md文本内容
+     * @param string $childrenField 为tree类型时指定children字段
+     * @param string $childrenDesc 为tree类型时指定children字段说明
+     * @param string $childrenType 为array类型时指定子节点类型
+     * @param string|array $field 指定Ref引入的字段
+     * @param string|array $withoutField 排除Ref引入的字段
+     * @param string $main 数据挂载节点
+     */
+    public function __construct(
+        $value = '',
+        string $type = '',
+        string $desc = '',
+        bool $require = false,
+        $ref = "",
+        string $md = "",
+        string $childrenField = "",
+        string $childrenDesc = "children",
+        string $childrenType = "",
+        $field = "",
+        $withoutField = "",
+        string $mock = "",
+        bool $main = false
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+
+}

+ 35 - 0
vendor/hg/apidoc/src/annotation/ResponseSuccessMd.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 成功响应体Markdown内容
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class ResponseSuccessMd extends AbstractAnnotation
+{
+    /**
+     * 引入md内容
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * @param string $name Markdown文档内容
+     * @param string $ref 引入md文件路径
+     */
+    public function __construct(
+        $name = '',
+        string $ref = ''
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+}

+ 62 - 0
vendor/hg/apidoc/src/annotation/Returned.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+
+/**
+ * 返回参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Returned extends ParamBase
+{
+
+    /**
+     * 是否替换全局响应体中的参数
+     * @var bool
+     */
+    public $replaceGlobal = false;
+
+    /**
+     * @param string $name 字段名
+     * @param string $type 字段类型
+     * @param bool $require 是否必须
+     * @param string|int|bool $default 默认值
+     * @param string|array $ref 引用注解/模型
+     * @param string $table 引用数据表
+     * @param string|array $field 指定Ref引入的字段
+     * @param string|array $withoutField 排除Ref引入的字段
+     * @param string $desc 字段名称
+     * @param string $md Md文本内容
+     * @param array $children 子参数
+     * @param string $childrenField 为tree类型时指定children字段
+     * @param string $childrenDesc 为tree类型时指定children字段说明
+     * @param string $childrenType 为array类型时指定子节点类型
+     * @param string $replaceGlobal 是否替换全局响应体参数
+     */
+    public function __construct(
+        $name = '',
+        string $type = '',
+        bool $require = false,
+        $default = "",
+        $ref = "",
+        $table = "",
+        $field = "",
+        $withoutField = "",
+        string $desc = '',
+        string $md = "",
+        string $mock = "",
+        array $children = [],
+        string $childrenField = "",
+        string $childrenDesc = "children",
+        string $childrenType = "",
+        bool $replaceGlobal = false,
+        ...$attrs
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+}

+ 26 - 0
vendor/hg/apidoc/src/annotation/RouteMiddleware.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 路由中间件,自动注册路由时有效
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","CLASS"})
+ */
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class RouteMiddleware extends AbstractAnnotation
+{
+    /**
+     * 路由中间件,自动注册路由时有效
+     * @param array $value
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 51 - 0
vendor/hg/apidoc/src/annotation/RouteParam.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+
+/**
+ * 路由参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class RouteParam extends ParamBase
+{
+    /**
+     * @param string $name 字段名
+     * @param string $type 字段类型
+     * @param bool $require 是否必须
+     * @param string|int|bool $default 默认值
+     * @param string|array $ref 引用注解/模型
+     * @param string|array $field 指定Ref引入的字段
+     * @param string|array $withoutField 排除Ref引入的字段
+     * @param string $desc 字段名称
+     * @param string $md Md文本内容
+     * @param string $mock Mock规则
+     */
+    public function __construct(
+        $name = '',
+        string $type = '',
+        bool $require = false,
+        $default = "",
+        $ref = "",
+        $field = "",
+        $withoutField = "",
+        string $desc = '',
+        string $md = "",
+        string $mock = "",
+        ...$attrs
+    )
+    {
+        parent::__construct(...func_get_args());
+    }
+
+    /**
+     * mock
+     * @var string
+     */
+    public $mock;
+
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/Sort.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 排序
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"CLASS"})
+ */
+#[Attribute(Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
+class Sort extends AbstractAnnotation
+{
+    /**
+     * @param string|int $value 排序
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/Tag.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * Tag
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Tag extends AbstractAnnotation
+{
+    /**
+     * @param string $value Tag
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/Title.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 标题
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","CLASS"})
+ */
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Title extends AbstractAnnotation
+{
+    /**
+     * @param string $name 控制器/接口名称
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/Url.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 接口Url
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class Url extends AbstractAnnotation
+{
+    /**
+     * @param string $value 接口Url
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 25 - 0
vendor/hg/apidoc/src/annotation/WithoutField.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Attribute;
+use Doctrine\Common\Annotations\Annotation;
+use hg\apidoc\utils\AbstractAnnotation;
+
+/**
+ * 排除Ref的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+#[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class WithoutField extends AbstractAnnotation
+{
+    /**
+     * @param string|array $value 排除Ref的字段,逗号分割
+     */
+    public function __construct(...$value)
+    {
+        parent::__construct(...$value);
+    }
+}

+ 94 - 0
vendor/hg/apidoc/src/config.php

@@ -0,0 +1,94 @@
+<?php
+return [
+    // (选配)文档标题,显示在左上角与首页
+    'title'              => 'Apidoc',
+    // (选配)文档描述,显示在首页
+    'desc'               => '',
+    // (必须)设置文档的应用/版本
+    'apps'           => [
+        [
+            // (必须)标题
+            'title'=>'Api接口',
+            // (必须)控制器目录地址
+            'path'=>'app\controller',
+            // (必须)唯一的key
+            'key'=>'api',
+        ]
+    ],
+    // (必须)指定通用注释定义的文件地址
+    'definitions'        => "app\common\controller\Definitions",
+    // (必须)自动生成url规则,当接口不添加@Apidoc\Url ("xxx")注解时,使用以下规则自动生成
+    'auto_url' => [
+        // 字母规则,lcfirst=首字母小写;ucfirst=首字母大写;
+        'letter_rule' => "lcfirst",
+        // url前缀
+        'prefix'=>"",
+    ],
+    // 是否自动注册路由
+    'auto_register_routes'=>false,
+    // (必须)缓存配置
+    'cache'              => [
+        // 是否开启缓存
+        'enable' => false,
+    ],
+    // (必须)权限认证配置
+    'auth'               => [
+        // 是否启用密码验证
+        'enable'     => false,
+        // 全局访问密码
+        'password'   => "123456",
+        // 密码加密盐
+        'secret_key' => "apidoc#hg_code",
+        // 授权访问后的有效期
+        'expire' => 24*60*60
+    ],
+    // 全局参数
+    'params'=>[
+        // (选配)全局的请求Header
+        'header'=>[
+            // name=字段名,type=字段类型,require=是否必须,default=默认值,desc=字段描述
+            ['name'=>'Authorization','type'=>'string','require'=>true,'desc'=>'身份令牌Token'],
+        ],
+        // (选配)全局的请求Query
+        'query'=>[
+            // 同上 header
+        ],
+        // (选配)全局的请求Body
+        'body'=>[
+            // 同上 header
+        ],
+    ],
+    // 全局响应体
+    'responses'=>[
+        // 成功响应体
+        'success'=>[
+            ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1],
+            ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
+            //参数同上 headers;main=true来指定接口Returned参数挂载节点
+            ['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object','require'=>1],
+        ],
+        // 异常响应体
+        'error'=>[
+            ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1,'md'=>'/docs/HttpError.md'],
+            ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
+        ]
+    ],
+    //(选配)默认作者
+    'default_author'=>'',
+    //(选配)默认请求类型
+    'default_method'=>'GET',
+    //(选配)允许跨域访问
+    'allowCrossDomain'=>false,
+    /**
+     * (选配)解析时忽略带@注解的关键词,当注解中存在带@字符并且非Apidoc注解,如 @key test,此时Apidoc页面报类似以下错误时:
+     * [Semantical Error] The annotation "@key" in method xxx() was never imported. Did you maybe forget to add a "use" statement for this annotation?
+     */
+    'ignored_annitation'=>[],
+
+    // (选配)数据库配置
+    'database'=>[],
+    // (选配)Markdown文档
+    'docs'              => [],
+    // (选配)代码生成器配置 注意:是一个二维数组
+    'generator' =>[]
+];

+ 97 - 0
vendor/hg/apidoc/src/config/plugin/hg/apidoc/app.php

@@ -0,0 +1,97 @@
+<?php
+return [
+    'enable'  => true,
+    'apidoc' => [
+        // (选配)文档标题,显示在左上角与首页
+        'title'              => 'Apidoc',
+        // (选配)文档描述,显示在首页
+        'desc'               => '',
+        // (必须)设置文档的应用/版本
+        'apps'           => [
+            [
+                // (必须)标题
+                'title'=>'Api接口',
+                // (必须)控制器目录地址
+                'path'=>'app\controller',
+                // (必须)唯一的key
+                'key'=>'api',
+            ]
+        ],
+        // (必须)指定通用注释定义的文件地址
+        'definitions'        => "app\common\controller\Definitions",
+        // (必须)自动生成url规则,当接口不添加@Apidoc\Url ("xxx")注解时,使用以下规则自动生成
+        'auto_url' => [
+            // 字母规则,lcfirst=首字母小写;ucfirst=首字母大写;
+            'letter_rule' => "lcfirst",
+            // url前缀
+            'prefix'=>"",
+        ],
+        // (选配)是否自动注册路由
+        'auto_register_routes'=>false,
+        // (必须)缓存配置
+        'cache'              => [
+            // 是否开启缓存
+            'enable' => false,
+        ],
+        // (必须)权限认证配置
+        'auth'               => [
+            // 是否启用密码验证
+            'enable'     => false,
+            // 全局访问密码
+            'password'   => "123456",
+            // 密码加密盐
+            'secret_key' => "apidoc#hg_code",
+            // 授权访问后的有效期
+            'expire' => 24*60*60
+        ],
+        // 全局参数
+        'params'=>[
+            // (选配)全局的请求Header
+            'header'=>[
+                // name=字段名,type=字段类型,require=是否必须,default=默认值,desc=字段描述
+                ['name'=>'Authorization','type'=>'string','require'=>true,'desc'=>'身份令牌Token'],
+            ],
+            // (选配)全局的请求Query
+            'query'=>[
+                // 同上 header
+            ],
+            // (选配)全局的请求Body
+            'body'=>[
+                // 同上 header
+            ],
+        ],
+        // 全局响应体
+        'responses'=>[
+            // 成功响应体
+            'success'=>[
+                ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1],
+                ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
+                //参数同上 headers;main=true来指定接口Returned参数挂载节点
+                ['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object','require'=>1],
+            ],
+            // 异常响应体
+            'error'=>[
+                ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1,'md'=>'/docs/HttpError.md'],
+                ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
+            ]
+        ],
+        //(选配)默认作者
+        'default_author'=>'',
+        //(选配)默认请求类型
+        'default_method'=>'GET',
+        //(选配)Apidoc允许跨域访问
+        'allowCrossDomain'=>false,
+        /**
+         * (选配)解析时忽略带@注解的关键词,当注解中存在带@字符并且非Apidoc注解,如 @key test,此时Apidoc页面报类似以下错误时:
+         * [Semantical Error] The annotation "@key" in method xxx() was never imported. Did you maybe forget to add a "use" statement for this annotation?
+         */
+        'ignored_annitation'=>[],
+
+        // (选配)数据库配置
+        'database'=>[],
+        // (选配)Markdown文档
+        'docs'              => [],
+        // (选配)接口生成器配置 注意:是一个二维数组
+        'generator' =>[]
+    ]
+];

+ 3 - 0
vendor/hg/apidoc/src/config/plugin/hg/apidoc/route.php

@@ -0,0 +1,3 @@
+<?php
+// 注册Apidoc路由
+hg\apidoc\providers\WebmanService::register();

+ 62 - 0
vendor/hg/apidoc/src/exception/ErrorException.php

@@ -0,0 +1,62 @@
+<?php
+
+
+namespace hg\apidoc\exception;
+
+
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\Helper;
+
+class ErrorException extends HttpException
+{
+
+    protected $exceptions = [
+        'apidoc close'     => ['status'=>404,'code' => 4004, 'msg' => '文档已关闭'],
+        'password error'     => ['status'=>402,'code' => 4002, 'msg' => '密码不正确,请重新输入'],
+        'password not found' => ['status'=>402,'code' => 4002, 'msg' => '密码不可为空'],
+        'token error'        => ['status'=>401,'code' => 4001, 'msg' => '不合法的Token'],
+        'token not found'    => ['status'=>401,'code' => 4001, 'msg' => '不存在Token'],
+        'appkey not found'     => ['status'=>412,'code' => 4005, 'msg' => '缺少必要参数appKey'],
+        'mdPath not found'     => ['status'=>412,'code' => 4006, 'msg' => '缺少必要参数path'],
+        'appKey error'         => ['status'=>412,'code' => 4007, 'msg' => '不存在 key为${appKey}的apps配置'],
+        'template not found'   => ['status'=>412,'code' => 4008, 'msg' => '${template}模板不存在'],
+        'path not found'       => ['status'=>412,'code' => 4009, 'msg' => '${path}目录不存在'],
+        'classname error'      => ['status'=>412,'code' => 4010, 'msg' => '${classname}文件名不合法'],
+        'apiKey not found'     => ['status'=>412,'code' => 4011, 'msg' => '缺少必要参数apiKey'],
+        'no config apps'       => ['status'=>412,'code' => 5000, 'msg' => 'apps配置不可为空'],
+        'unknown error'       => ['status'=>501,'code' => 5000, 'msg' => '未知异常'],
+        'no debug'             => ['status'=>412,'code' => 5001, 'msg' => '请在debug模式下,使用该功能'],
+        'no config crud'       => ['status'=>412,'code' => 5002, 'msg' => 'crud未配置'],
+        'datatable crud error' => ['status'=>412,'code' => 5003, 'msg' => '数据表创建失败,请检查配置'],
+        'file already exists' => ['status'=>412,'code' => 5004, 'msg' => '${filepath}文件已存在'],
+        'file not exists' => ['status'=>412,'code' => 5005, 'msg' => '${filepath}文件不存在'],
+        'datatable already exists' => ['status'=>412,'code' => 5004, 'msg' => '数据表${table}已存在'],
+        'datatable not exists' => ['status'=>412,'code' => 5004, 'msg' => '数据表${table}不存在'],
+        'ref file not exists' => ['status'=>412,'code' => 5005, 'msg' => 'ref引入 ${path} 文件不存在'],
+        'ref method not exists' => ['status'=>412,'code' => 5005, 'msg' => 'ref引入${path} 中 ${method} 方法不存在'],
+        'datatable create error' => ['status'=>412,'code' => 5006, 'msg' => '数据表[${table}]创建失败,error:${message},sql:${sql}'],
+    ];
+
+    public function __construct(string $exceptionCode, array $data = [])
+    {
+        $config = ConfigProvider::get();
+        $exception = $this->getException($exceptionCode);
+        if ($exception){
+            $msg       = Helper::replaceTemplate($exception['msg'], $data);
+        }else{
+            $exception = $this->exceptions['unknown error'];
+            $msg = $exceptionCode;
+        }
+        parent::__construct($exception['status'], $msg, null, [], $exception['code']);
+
+    }
+
+    public function getException($exceptionCode)
+    {
+        if (isset($this->exceptions[$exceptionCode])) {
+            return $this->exceptions[$exceptionCode];
+        }
+        return null;
+    }
+
+}

+ 33 - 0
vendor/hg/apidoc/src/exception/HttpException.php

@@ -0,0 +1,33 @@
+<?php
+declare (strict_types=1);
+
+namespace hg\apidoc\exception;
+
+use Exception;
+
+/**
+ * HTTP异常
+ */
+class HttpException extends \RuntimeException
+{
+    private $statusCode;
+    private $headers;
+
+    public function __construct(int $statusCode, string $message = '', Exception $previous = null, array $headers = [], $code = 0)
+    {
+        $this->statusCode = $statusCode;
+        $this->headers = $headers;
+
+        parent::__construct($message, $code, $previous);
+    }
+
+    public function getStatusCode()
+    {
+        return $this->statusCode;
+    }
+
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+}

+ 354 - 0
vendor/hg/apidoc/src/generator/Index.php

@@ -0,0 +1,354 @@
+<?php
+
+namespace hg\apidoc\generator;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\generator\ParseTemplate;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+
+class Index
+{
+    protected $config = [];
+
+    protected $middlewares = [];
+
+    protected $databaseConfig = [
+        // 数据库表前缀
+        'prefix'          => '',
+        // 数据库编码,默认为utf8
+        'charset'         =>  'utf8',
+        // 数据库引擎,默认为 InnoDB
+        'engine'          => 'InnoDB',
+    ];
+
+    protected  $systemDefaultValues = [
+        'CURRENT_TIMESTAMP'
+    ];
+
+    public function __construct($config)
+    {
+        $this->config = $config;
+        if (!empty($config['database'])){
+            if (!empty($config['database']['prefix'])){
+                $this->databaseConfig['prefix'] = $config['database']['prefix'];
+            }
+            if (!empty($config['database']['charset'])){
+                $this->databaseConfig['charset'] = $config['database']['charset'];
+            }
+            if (!empty($config['database']['engine'])){
+                $this->databaseConfig['engine'] = $config['database']['engine'];
+            }
+        }
+    }
+
+    public function create($params){
+        $appKey = $params['form']['appKey'];
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $currentApps = $currentAppConfig['apps'];
+        $currentApp  = $currentAppConfig['appConfig'];
+        $generatorItem = $this->config['generator'][$params['index']];
+
+        $checkParams = $this->checkFilesAndHandleParams($generatorItem,$params,$currentApps);
+        $tplParams = $checkParams['tplParams'];
+        // 注册中间件并执行before
+        if (!empty($generatorItem['middleware']) && count($generatorItem['middleware'])){
+            foreach ($generatorItem['middleware'] as $middleware) {
+                $instance = new $middleware;
+                $this->middlewares[] = $instance;
+                if (method_exists($instance, 'before')) {
+                    $middlewareRes = $instance->before($tplParams);
+                    if (!empty($middlewareRes)){
+                        $tplParams = $middlewareRes;
+                    }
+                }
+            }
+        }
+
+        $this->createModels($checkParams['createModels'],$tplParams);
+        $this->createFiles($checkParams['createFiles'],$tplParams);
+         //执行after
+        if (count($this->middlewares)){
+            foreach ($this->middlewares as $middleware) {
+                if (method_exists($instance, 'after')) {
+                    $instance->after($tplParams);
+                }
+            }
+        }
+        return $tplParams;
+    }
+
+    /**
+     * 验证文件及处理模板数据
+     * @param $generatorItem
+     * @param $params
+     * @param $currentApps
+     * @return array
+     */
+    protected function checkFilesAndHandleParams($generatorItem,$params,$currentApps){
+        // 组成模板参数
+        $tplParams=[
+            'form'=>$params['form'],
+            'tables'=>$params['tables'],
+            'app'=>$currentApps
+        ];
+        $createFiles = [];
+        if (!empty($params['files']) && count($params['files'])>0) {
+            $files = $params['files'];
+            foreach ($files as $file) {
+                $fileConfig = Helper::getArrayFind($generatorItem['files'], function ($item) use ($file) {
+                    if ($file['name'] === $item['name']) {
+                        return true;
+                    }
+                    return false;
+                });
+
+                $filePath = Helper::replaceCurrentAppTemplate($fileConfig['path'], $currentApps);
+                if (!empty($fileConfig['namespace'])) {
+                    $fileNamespace = Helper::replaceCurrentAppTemplate($fileConfig['namespace'], $currentApps);
+                } else {
+                    $fileNamespace = $filePath;
+                }
+                $fileNamespaceEndStr = substr($fileNamespace, -1);
+                if ($fileNamespaceEndStr == '\\') {
+                    $fileNamespace = substr($fileNamespace, 0, strlen($fileNamespace) - 1);
+                }
+                $template = Helper::replaceCurrentAppTemplate($fileConfig['template'], $currentApps);
+                $tplParams[$file['name']] = [
+                    'class_name' => $file['value'],
+                    'path' => $filePath,
+                    'namespace' => $fileNamespace,
+                    'template' => $template
+                ];
+
+                // 验证模板是否存在
+                $templatePath =DirAndFile::formatPath( APIDOC_ROOT_PATH . $template,"/");
+                if (is_readable($templatePath) == false) {
+                    throw new ErrorException("template not found",  [
+                        'template' => $template
+                    ]);
+                }
+                // 验证是否已存在生成的文件
+                $fileFullPath = DirAndFile::formatPath(APIDOC_ROOT_PATH . $filePath, "/");
+                $type = "folder";
+                if (strpos($fileFullPath, '.php') !== false) {
+                    // 路径为php文件,则验证文件是否存在
+                    if (is_readable($fileFullPath) == false) {
+                        throw new ErrorException("file not exists",  [
+                            'filepath' => $filePath
+                        ]);
+                    }
+                    $type = "file";
+                } else {
+                    $fileName = !empty($file['value']) ? $file['value'] : "";
+                    $fileFullPath = $fileFullPath . "/" . $fileName . ".php";
+                    if (is_readable($fileFullPath)) {
+                        throw new ErrorException("file already exists",[
+                            'filepath' => DirAndFile::formatPath($filePath) . $fileName . ".php"
+                        ]);
+                    }
+                }
+                $createFiles[] = [
+                    'fileFullPath' => $fileFullPath,
+                    'template' => $template,
+                    'templatePath'=>$templatePath,
+                    'type' => $type
+                ];
+            }
+        }
+
+        $createModels = $this->checkModels($generatorItem,$tplParams,$currentApps);
+        $tplParams['tables'] = $createModels['tables'];
+        return [
+            'tplParams'=>$tplParams,
+            'createFiles'=>$createFiles,
+            'createModels' =>$createModels['createModels']
+        ];
+    }
+
+    /**
+     * 验证模型及表
+     * @param $generatorItem
+     * @param $tplParams
+     * @return array
+     */
+    protected function checkModels($generatorItem,$tplParams,$currentApps){
+        if (empty($this->config['database_query_function'])){
+            throw new ErrorException("not datatable_query_function config");
+        }
+
+        $res="";
+        $tabls = $tplParams['tables'];
+        $newTables = [];
+        $createModels = [];
+        if (!empty($tabls) && count($tabls)){
+            foreach ($tabls as $k=>$table) {
+                $tableConfig = $generatorItem['table'];
+                $fileFullPath="";
+                if (!empty($table['model_name'])){
+                    $namespace = Helper::replaceCurrentAppTemplate($tableConfig['items'][$k]['namespace'], $currentApps);
+                    $path = Helper::replaceCurrentAppTemplate($tableConfig['items'][$k]['path'], $currentApps);
+                    $template = $tableConfig['items'][$k]['template'];
+
+                    // 验证模板是否存在
+                    $templatePath = DirAndFile::formatPath(APIDOC_ROOT_PATH . $template,"/");
+                    if (is_readable($templatePath) == false) {
+                        throw new ErrorException("template not found", [
+                            'template' => $template
+                        ]);
+                    }
+                    $tplParams['tables'][$k]['class_name'] =$table['model_name'];
+                    // 验证模型是否已存在
+                    $fileName = $table['model_name'];
+                    $fileFullPath = DirAndFile::formatPath(APIDOC_ROOT_PATH.$path). "/" . $fileName . ".php";
+                    if (is_readable($fileFullPath)) {
+                        throw new ErrorException("file already exists", [
+                            'filepath' => DirAndFile::formatPath($path) . "/" . $fileName . ".php"
+                        ]);
+                    }
+                }
+                // 验证表是否存在
+                if ($table['table_name']){
+                    $table_name = $this->databaseConfig['prefix'].$table['table_name'];
+                    $isTable = $this->config['database_query_function']('SHOW TABLES LIKE '."'".$table_name."'");
+                    if ($isTable){
+                        throw new ErrorException("datatable already exists",  [
+                            'table' => $table_name
+                        ]);
+                    }
+                }
+                $table['namespace']=$namespace;
+                $table['path']=$path;
+                $table['model_path']=$path;
+                $newTables[]=$table;
+                $createModels[]=[
+                    'namespace'=>$namespace,
+                    'template'=>$template,
+                    'path'=>$path,
+                    'templatePath' =>$templatePath,
+                    'table'=>$table,
+                    'fileFullPath'=>$fileFullPath
+                ];
+            }
+        }
+        return ['createModels'=>$createModels,'tables'=>$newTables];
+
+    }
+
+    /**
+     * 创建文件
+     * @param $createFiles
+     * @param $tplParams
+     * @return mixed
+     */
+    protected function createFiles($createFiles,$tplParams){
+
+        if (!empty($createFiles) && count($createFiles)>0){
+            foreach ($createFiles as $fileItem) {
+                $html = (new ParseTemplate())->compile($fileItem['templatePath'],$tplParams);
+                if ($fileItem['type'] === "file"){
+                    // 路径为文件,则添加到该文件
+                    $pathFileContent = DirAndFile::getFileContent($fileItem['fileFullPath']);
+                    $content = $pathFileContent."\r\n".$html;
+                    DirAndFile::createFile($fileItem['fileFullPath'],$content);
+                }else{
+                    DirAndFile::createFile($fileItem['fileFullPath'],$html);
+                }
+            }
+        }
+        return $tplParams;
+    }
+
+    /**
+     * 创建模型文件
+     * @param $createModels
+     * @param $tplParams
+     */
+    protected function createModels($createModels,$tplParams){
+        if (!empty($createModels) && count($createModels)>0){
+            foreach ($createModels as $k=>$item) {
+                $table = $item['table'];
+                if (!empty($table['table_name'])){
+                    $res =  $this->createTable($table);
+                }
+                if (!empty($table['model_name'])){
+                    $tplParams['tables'][$k]['class_name'] =$table['model_name'];
+                    $html = (new ParseTemplate())->compile($item['templatePath'],$tplParams);
+                    DirAndFile::createFile($item['fileFullPath'],$html);
+                }
+
+            }
+        }
+    }
+
+    /**
+     * 创建数据表
+     * @return mixed
+     */
+    protected function createTable($table){
+        $datas = $table['datas'];
+        $comment= "";
+        if (!empty($table['table_comment'])){
+            $comment =$table['table_comment'];
+        }
+        $table_name = $this->databaseConfig['prefix'].$table['table_name'];
+        $table_data = '';
+        $main_keys = '';
+        $defaultNullTypes = ['timestamp'];
+        foreach ($datas as $k=>$item){
+            if (!empty($item['not_table_field'])){
+                continue;
+            }
+            $table_field="`".$item['field']."` ".$item['type'];
+            if (!empty($item['length'])){
+                $table_field.="(".$item['length'].")";
+            }
+
+            if (!empty($item['main_key'])){
+                $main_keys.=$item['field'];
+                $table_field.=" NOT NULL";
+            }else if (!empty($item['not_null'])){
+                $table_field.=" NOT NULL";
+            }
+            if (!empty($item['incremental']) && !empty($item['main_key'])){
+                $table_field.=" AUTO_INCREMENT";
+            }
+            if (!empty($item['default']) || (isset($item['default']) && $item['default']=="0")){
+                $defaultValue = "'".$item['default']."'";
+                if (in_array($item['default'],$this->systemDefaultValues)){
+                    $defaultValue = $item['default'];
+                }
+                $table_field.=" DEFAULT ".$defaultValue."";
+            }else if (!empty($item['main_key']) && !$item['not_null']){
+                $table_field.=" DEFAULT NULL";
+            }else if (in_array($item['type'],$defaultNullTypes) && empty($item['not_null'])){
+                $table_field.=" NULL DEFAULT NULL";
+            }
+            $fh = $k < (count($datas)-1)?",":"";
+            $table_field.=" COMMENT '".$item['desc']."'".$fh;
+            $table_data.=$table_field;
+        }
+        $primaryKey = "";
+        if (!empty($main_keys)){
+            $table_data.=",";
+            $primaryKey = "PRIMARY KEY (`$main_keys`)";
+        }
+
+        $charset = $this->databaseConfig['charset'];
+        $engine = $this->databaseConfig['engine'];
+        $sql = "CREATE TABLE IF NOT EXISTS `$table_name` (
+        $table_data
+        $primaryKey
+        ) ENGINE=$engine DEFAULT CHARSET=$charset COMMENT='$comment' AUTO_INCREMENT=1 ;";
+
+        try {
+            $this->config['database_query_function']($sql);
+            return true;
+        } catch (\Exception $e) {
+            throw new ErrorException("datatable create error",  [
+                'table' => $table_name,
+                'message'=>$e->getMessage(),
+                'sql'=>$sql
+            ]);
+        }
+    }
+}

+ 339 - 0
vendor/hg/apidoc/src/generator/ParseTemplate.php

@@ -0,0 +1,339 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\generator;
+use hg\apidoc\Utils;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use think\facade\App;
+
+class ParseTemplate
+{
+
+    public function compile($path,$params)
+    {
+        $filePath    =  $path;
+        $tplContent = DirAndFile::getFileContent($filePath);
+        $tplContent = $this->replaceForeach($tplContent,$params);
+        $tplContent = $this->replaceParams($tplContent,$params);
+        $tplContent = $this->replaceIf($tplContent,$params);
+        $tplContent = preg_replace("/\s+\r\n/is", "\r\n", $tplContent);
+        return $tplContent;
+    }
+
+    /**
+     * 替换变量
+     * @param $tplContent
+     * @param $params
+     * @return array|string|string[]|null
+     */
+    protected function replaceParams($tplContent,$params){
+        $key = '{$%%}';
+        $pattern = '#' . str_replace('%%', '(.+?)' , preg_quote($key, '#')) . '#';
+        $tplContent = preg_replace_callback($pattern, function ($matches)use ($params){
+            $k = $matches[1];
+            if (strpos($k, '(') !== false){
+                $tagArr = explode("(", $k);
+                $fun = $tagArr[0];
+                $k = str_replace(")", "",$tagArr[1] );
+                $v = $this->getObjectValueByKeys($params,$k);
+                if (empty($v)){
+                    return "";
+                }
+                if ($fun === "lower"){
+                   return Helper::lower($v);
+                }else if ($fun === "snake"){
+                    return Helper::snake($v);
+                }else if ($fun === "lcfirst"){
+                    return lcfirst($v);
+                }else if ($fun === "count"){
+                    return count($v);
+                }
+            }
+            $value = $this->getObjectValueByKeys($params,$k);
+            if (is_bool($value)){
+               return $value==true?'true':'false';
+            }else if (is_array($value)){
+               return $k;
+            }
+            return $value;
+        }, $tplContent);
+        return $tplContent;
+    }
+
+    /**
+     * 替换if内容
+     * @param $tplContent
+     * @param $params
+     * @return array|mixed|string|string[]
+     * @throws \Exception
+     */
+    protected function replaceIf($tplContent,$params){
+        $res = [];
+        $label = "if";
+        $labelList = $this->parseLabel($tplContent,$label);
+        if (!empty($labelList) && count($labelList)>0){
+            foreach ($labelList as $item) {
+                $itemStr =$item;
+                $ifChildren= $this->parseLabel($itemStr,$label,"children");
+
+                if (!empty($ifChildren) && count($ifChildren)>0){
+                    foreach ($ifChildren as $ifChild){
+                        $itemChildrenContent= $this->getIfContent($ifChild);
+                        $itemStr = str_replace($ifChild, $itemChildrenContent,$itemStr );
+                    }
+               }
+                $itemContent= $this->getIfContent($itemStr);
+                $tplContent =   str_replace($item, $itemContent,$tplContent );
+            }
+        }
+
+        return $tplContent;
+    }
+    protected function parseForeach($str,$params){
+        if (preg_match('#{foreach (.+?) as (.+?)=>(.+?)}#s', $str, $matches)){
+            $complete = $matches[0];
+            $condition = $matches[1];
+            $keyField = str_replace("$", "",$matches[2] );
+            $itemField = str_replace("$", "",$matches[3] );
+            $conditionKey = str_replace("$", "",$condition );
+            $forListData = $this->getObjectValueByKeys($params,$conditionKey);
+            $contentStr = str_replace($complete, "",$str);
+            $contentStr = substr($contentStr,0,-10);
+            return [
+                'list'=>$forListData,
+                'keyField'=>$keyField,
+                'itemField'=>$itemField,
+                'content'=>$contentStr
+            ];
+        }
+        return [];
+    }
+
+    /**
+     * 获取所有foreach标签
+     * @param $str
+     * @return array
+     * @throws \Exception
+     */
+    protected function getAllForeachLabel($str){
+        $tree = [];
+        $label = "foreach";
+        $labelList = $this->parseLabel($str,$label);
+        if (!empty($labelList) && count($labelList)>0){
+            foreach ($labelList as $itemLabel) {
+                $labelChildrenList = $this->parseLabel($itemLabel,$label,"children");
+                if (!empty($labelChildrenList) && count($labelChildrenList)>0){
+                    $childrenList = [];
+                    foreach ($labelChildrenList as $item) {
+                        $childrenList[]=[
+                            'str'=>$item,
+                            'children' => []
+                        ];
+                    }
+                    $tree[]=[
+                        'str'=>$itemLabel,
+                        'children' => $childrenList
+                    ];
+                }else{
+                    $tree[]=[
+                        'str'=>$itemLabel,
+                        'children' => []
+                    ];
+                }
+            }
+        }
+        return $tree;
+    }
+    // 解析foreach
+    protected function replaceForeach($html,$params,$level=""){
+        $allLabelData= $this->getAllForeachLabel($html);
+        $res = [];
+        if (count($allLabelData)>0){
+            // 遍历每个foreach标签
+            foreach ($allLabelData as $labelItem) {
+                $itemStr = $labelItem['str'];
+                $forOption = $this->parseForeach($labelItem['str'],$params);
+                $itemContent="";
+                if (!empty($forOption['list']) && count($forOption['list'])>0){
+                    // 处理每行数据
+                    foreach ($forOption['list'] as $rowKey=>$row) {
+                        $rowData = [$forOption['itemField']=>$row,$forOption['keyField']=>$rowKey];
+                        $rowParams = array_merge($params,$rowData);
+                        // 存在子标签,处理子标签
+                        if (!empty($labelItem['children']) && count($labelItem['children'])>0){
+                            $itemStrContent = "";
+                            foreach ($labelItem['children'] as $childLabel){
+                                $childContents = "";
+                                $childStr = $childLabel['str'];
+                                $childDataList = $this->parseForeach($childLabel['str'],$rowParams);
+                                // 处理子标签数据
+                                if (!empty($childDataList['list']) && count($childDataList['list'])>0){
+                                    foreach ($childDataList['list'] as $childDataKey=>$childDataItem) {
+                                        // 子标签每行数据
+                                        $childDataItemData = [$childDataList['itemField']=>$childDataItem,$childDataList['keyField']=>$childDataKey,];
+                                        $contentsStr= $this->getForContent($childDataList['content'],array_merge($rowParams,$childDataItemData));
+                                        $contentsStr =ltrim($contentsStr,"\r\n");
+                                        if (!empty(Helper::trimEmpty($contentsStr))){
+                                            $childContents.= $contentsStr;
+                                        }
+                                    }
+                                }
+                                $itemStrContent.= str_replace($childLabel['str'], $childContents,$forOption['content']);
+                            }
+                            $rowContent=$this->replaceParams($itemStrContent,$rowParams);
+                            $itemContentStr=$this->replaceIf($rowContent,$rowParams);
+                            if (!empty(Helper::trimEmpty($itemContentStr))){
+                                $itemContent.= $itemContentStr;
+                            }
+                        }else{
+                            $rowContent=$this->getForContent($forOption['content'],$rowParams);
+                            if (empty(Helper::trimEmpty($rowContent))){
+                                $rowContent= "";
+                            }
+                            $itemContent.= $rowContent;
+                        }
+                        $itemContent =trim($itemContent,"\r\n");
+                    }
+                }
+                $html = str_replace($labelItem['str'], $itemContent,$html );
+            }
+        }
+        return $html;
+
+    }
+
+    /**
+     * 获取foreach内容
+     * @param $str
+     * @param $params
+     * @return array|mixed|string|string[]
+     * @throws \Exception
+     */
+    protected function getForContent($str,$params){
+            $content = $str;
+            if (!empty($params)){
+                $content = $this->replaceParams($content,$params);
+                $content = $this->replaceIf($content,$params);
+            }
+            return $content;
+    }
+
+
+    /**
+     * 获取if条件的内容
+     * @param $str
+     * @return mixed|string
+     */
+    protected function getIfContent($str){
+        if (preg_match('#{if (.+?)}(.*?){/if}#s', $str, $matches)){
+            if (eval("return $matches[1];")){
+                // 条件成立
+               return $matches[2];
+            }
+        }
+        return "";
+    }
+
+
+    /**
+     * 解析指定标签
+     * @param $str
+     * @param $label
+     * @param string $level
+     * @return array
+     * @throws \Exception
+     */
+    protected function parseLabel($str,$label,$level=""){
+        // 后面的 flag 表示记录偏移量
+        preg_match_all('!({/?'.$label.' ?}?)!', $str, $matches, PREG_OFFSET_CAPTURE);
+        // 用数组来模拟栈
+        $stack  = [];
+        $top    = null;
+        $result = [];
+        foreach ($matches[0] as $k=>[$match, $offset]) {
+            // 当取标签内容时,排除第一个和最后一个标签
+            if ($level === 'children' && ($k==0 || $k>=count($matches[0])-1)){
+                continue;
+            }
+            // 判断匹配到的如果是 开始标签
+            if ($match === '{'.$label.' ') {
+                $stack[] = $offset;
+                // 记录开始的位置
+                if ($top === null) {
+                    $top = $offset;
+                }
+                // 如果不是
+            } else {
+                // 从栈底部拿一个出来
+                $pop = array_pop($stack);
+                // 如果取出来的是 null 就说明存在多余的 标签
+                if ($pop === null) {
+                    throw new \Exception('语法错误,存在多余的 {/'.$label.'} 标签');
+                }
+                // 如果取完后栈空了
+                if (empty($stack)) {
+                    // offset 是匹配到的开始标签(前面)位置,加上内容的长度
+                    $newOffset = $offset + strlen($match)-$top;
+                    // 从顶部到当前的偏移就是这个标签里的内容
+                    $result[] = substr($str, $top, $newOffset);
+                    // 重置 top 开始下一轮
+                    $top = null;
+                }
+            }
+        }
+        // 如果运行完了,栈里面还有东西,那就说明缺少闭合标签。
+        if (!empty($stack)) {
+            throw new \Exception('语法错误,存在未闭合的 {/'.$label.'} 标签');
+        }
+        return $result;
+    }
+
+    /**
+     * 根据keys获取对象中的值
+     * @param $array
+     * @param $keyStr
+     * @param string $delimiter
+     * @return mixed|null
+     */
+    public function getObjectValueByKeys($array, $keyStr, $delimiter = '.')
+    {
+        $keys = explode($delimiter, $keyStr);
+        if (preg_match_all('#\[(.+?)]#s', $keyStr, $matches)){
+            $value = $array;
+            if (!empty($matches[1])){
+                $matchesIndex=0;
+                foreach ($keys as $keyItem) {
+                    if (strpos($keyItem, '[') !== false) {
+                        $tagArr = explode("[", $keyItem);
+                        if (!empty($value[$tagArr[0]]) && !empty($value[$tagArr[0]][$matches[1][$matchesIndex]])){
+                            $value =$value[$tagArr[0]][$matches[1][$matchesIndex]];
+                        }else{
+                            $value =null;
+                            break;
+                        }
+                        $matchesIndex=$matchesIndex+1;
+                    }else{
+                        $value =$value[$keyItem];
+                    }
+                }
+            }
+            return $value;
+        }else if (sizeof($keys) > 1) {
+            $value = $array;
+            foreach ($keys as $key){
+                if (!empty($value[$key])){
+                    $value = $value[$key];
+                }else{
+                    $value =null;
+                    break;
+                }
+            }
+            return $value;
+        } else {
+            return $array[$keyStr] ?? null;
+        }
+    }
+
+
+}

+ 81 - 0
vendor/hg/apidoc/src/middleware/HyperfMiddleware.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace hg\apidoc\middleware;
+
+use hg\apidoc\providers\BaseService;
+use hg\apidoc\utils\ConfigProvider;
+use Hyperf\DbConnection\Db;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+class HyperfMiddleware
+{
+    use BaseService;
+
+    static $langLocale="zh_CN";
+
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $this->initConfig();
+
+        if ($request->getMethod() == "GET"){
+            $params = $request->getQueryParams();
+        }else{
+            $params = $request->getParsedBody();
+        }
+        $config =  ConfigProvider::get();
+        $config['request_params'] = $params;
+        ConfigProvider::set($config);
+        return $handler->handle($request);
+    }
+
+    static function getApidocConfig()
+    {
+        $config = config("apidoc");
+        if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
+            $config['auto_url']['filter_keys'] = ['App','Controller'];
+        }
+        $config['app_frame'] = "hyperf";
+        return $config;
+    }
+
+    static function registerRoute($route)
+    {
+        // TODO: Implement registerRoute() method.
+    }
+
+    static function databaseQuery($sql)
+    {
+        return Db::select($sql);
+    }
+
+    static function getRootPath()
+    {
+        return BASE_PATH."/";
+    }
+
+    static function getRuntimePath()
+    {
+        return BASE_PATH."/runtime/";
+    }
+
+    static function setLang($locale)
+    {
+        static::$langLocale = $locale;
+    }
+
+    static function getLang($lang): string
+    {
+        return trans($lang);
+    }
+
+    static function handleResponseJson($res)
+    {
+        return $res;
+    }
+
+    static function getTablePrefix(){
+        return config('databases.default.prefix','');
+    }
+}

+ 17 - 0
vendor/hg/apidoc/src/middleware/LaravelMiddleware.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace hg\apidoc\middleware;
+
+use hg\apidoc\utils\ConfigProvider;
+
+class LaravelMiddleware
+{
+    public function handle($request, \Closure $next)
+    {
+        $params = $request->all();
+        $config =  ConfigProvider::get();
+        $config['request_params'] = $params;
+        ConfigProvider::set($config);
+        return $next($request);
+    }
+}

+ 17 - 0
vendor/hg/apidoc/src/middleware/ThinkPHPMiddleware.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace hg\apidoc\middleware;
+
+use hg\apidoc\utils\ConfigProvider;
+
+class ThinkPHPMiddleware
+{
+    public function handle($request, \Closure $next)
+    {
+        $params = $request->param();
+        $config =  ConfigProvider::get();
+        $config['request_params'] = $params;
+        ConfigProvider::set($config);
+        return $next($request);
+    }
+}

+ 88 - 0
vendor/hg/apidoc/src/middleware/WebmanMiddleware.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace hg\apidoc\middleware;
+
+use hg\apidoc\providers\BaseService;
+use hg\apidoc\utils\ConfigProvider;
+use support\Db;
+use Webman\MiddlewareInterface;
+use Webman\Http\Response;
+use Webman\Http\Request;
+
+class WebmanMiddleware implements MiddlewareInterface
+{
+    use BaseService;
+
+    public function process(Request $request, callable $handler) : Response
+    {
+        $this->initConfig();
+        $params = $request->all();
+        $config =  ConfigProvider::get();
+        $config['request_params'] = $params;
+        ConfigProvider::set($config);
+
+        $response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
+        if (!empty($config['allowCrossDomain'])){
+            // 给响应添加跨域相关的http头
+            $response->withHeaders([
+                'Access-Control-Allow-Credentials' => 'true',
+                'Access-Control-Allow-Origin' => $request->header('origin', '*'),
+                'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
+                'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
+            ]);
+        }
+
+        return $response;
+    }
+
+    static function getApidocConfig()
+    {
+        $config = config('plugin.hg.apidoc.app.apidoc');
+        if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
+            $config['auto_url']['filter_keys'] = ['app','controller'];
+        }
+        $config['app_frame'] = "webman";
+        return $config;
+    }
+
+    static function registerRoute($route)
+    {
+        return "";
+    }
+
+    static function databaseQuery($sql)
+    {
+        return Db::select($sql);
+    }
+
+    static function getRootPath()
+    {
+        return BASE_PATH."/";
+    }
+
+    static function getRuntimePath()
+    {
+        return BASE_PATH."/runtime/";
+    }
+
+    static function setLang($locale)
+    {
+        locale($locale);
+    }
+
+    static function getLang($lang): string
+    {
+        return $lang;
+    }
+
+    static function handleResponseJson($res)
+    {
+        return json($res);
+    }
+
+    static function getTablePrefix(){
+        $driver = config('database.default');
+        $table_prefix=config('database.connections.'.$driver.'.prefix');
+        return $table_prefix;
+    }
+}

+ 142 - 0
vendor/hg/apidoc/src/parses/ParseAnnotation.php

@@ -0,0 +1,142 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use Doctrine\Common\Annotations\AnnotationReader;
+use hg\apidoc\utils\Helper;
+use ReflectionAttribute;
+use hg\apidoc\exception\ErrorException;
+use ReflectionMethod;
+use think\facade\Log;
+
+class ParseAnnotation
+{
+
+    protected $parser;
+
+    public function __construct($config)
+    {
+        $this->parser = new AnnotationReader();
+        if (!empty($config['ignored_annitation'])){
+            foreach ($config['ignored_annitation'] as $item) {
+                AnnotationReader::addGlobalIgnoredName($item);
+            }
+        }
+    }
+    /**
+     * 解析非@注解的文本注释
+     * @param $refMethod
+     * @return array|false
+     */
+    public static function parseTextAnnotation($refMethod): array
+    {
+        $annotation = $refMethod->getDocComment();
+        if (empty($annotation)) {
+            return [];
+        }
+        if (preg_match('#^/\*\*(.*)\*/#s', $annotation, $comment) === false)
+            return [];
+        $comment = trim($comment [1]);
+        if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false)
+            return [];
+        $data = [];
+        foreach ($lines[1] as $line) {
+            $line = trim($line);
+            if (!empty ($line) && strpos($line, '@') !== 0) {
+                $data[] = $line;
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 根据路径获取类名
+     * @param $path
+     * @return string
+     */
+    protected function getClassName($path){
+        $NameArr = explode("\\", $path);
+        $name    = lcfirst($NameArr[count($NameArr) - 1]);
+        return $name;
+    }
+
+    /**
+     * 获取并处理注解参数
+     * @param $attrList
+     * @return array
+     */
+    protected function getParameters($attrList){
+        $attrs = [];
+        foreach ($attrList as $item) {
+            $value = "";
+            if ($item instanceof ReflectionAttribute) {
+                $name    = $this->getClassName($item->getName());
+                $params = $item->getArguments();
+                if (!empty($params)){
+                    if (is_array($params) && !empty($params[0]) && is_string($params[0]) && count($params)===1){
+                        $value = $params[0];
+                    }else{
+                        if (!empty($params[0])){
+                            $paramObj = [];
+                            foreach ($params as $k=>$value) {
+                                $key = $k===0?'name':$k;
+                                $paramObj[$key]=$value;
+                            }
+                        }else{
+                            $paramObj = $params;
+                        }
+                        $value = $paramObj;
+                    }
+                }
+            }else{
+                $name    = $this->getClassName(get_class($item));
+                $valueObj = Helper::objectToArray($item);
+                if (isset($valueObj['name']) && count($valueObj)===1){
+                    $value = $valueObj['name'];
+                }else{
+                    $value = $valueObj;
+                }
+            }
+            if (!empty($attrs[$name]) && is_array($attrs[$name]) && Helper::arrayKeyFirst($attrs[$name])===0){
+                $attrs[$name][]=$value;
+            }else if(!empty($attrs[$name])){
+                $attrs[$name] = [$attrs[$name],$value];
+            }else{
+                $attrs[$name]=$value;
+            }
+        }
+        return $attrs;
+    }
+
+    /**
+     * 获取类的注解参数
+     * @param ReflectionMethod $refMethod
+     * @return array
+     */
+    public function getClassAnnotation($refClass){
+        if (method_exists($refClass,'getAttributes')){
+            $attributes = $refClass->getAttributes();
+        }else{
+            $attributes = [];
+        }
+        $readerAttributes = $this->parser->getClassAnnotations($refClass);
+        return $this->getParameters(array_merge($attributes,$readerAttributes));
+    }
+
+    /**
+     * 获取方法的注解参数
+     * @param ReflectionMethod $refMethod
+     * @return array
+     */
+    public function getMethodAnnotation(ReflectionMethod $refMethod){
+        if (method_exists($refMethod,'getAttributes')){
+            $attributes = $refMethod->getAttributes();
+        }else{
+            $attributes = [];
+        }
+        $readerAttributes = $this->parser->getMethodAnnotations($refMethod);
+        return $this->getParameters(array_merge($attributes,$readerAttributes));
+    }
+
+}

+ 700 - 0
vendor/hg/apidoc/src/parses/ParseApiDetail.php

@@ -0,0 +1,700 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use ReflectionClass;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+
+class ParseApiDetail
+{
+
+    protected $config = [];
+
+    protected $currentApp = [];
+
+    protected $appKey;
+
+    public function __construct($config)
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * 生成api接口数据
+     * @param string $appKey
+     * @param string $apiKey
+     * @return array
+     */
+    public function renderApiDetail(string $appKey,string $apiKey)
+    {
+
+        $this->appKey = $appKey;
+        $pathArr   = explode("@", $apiKey);
+        $classPath = $pathArr[0];
+        $methodName = $pathArr[1];
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $this->currentApp  = $currentAppConfig['appConfig'];
+
+        try {
+            $refClass  = new ReflectionClass($classPath);
+            $refMethod= $refClass->getMethod($methodName);
+            $methodItem = $this->parseApiMethod($refClass,$refMethod);
+            return $methodItem;
+        } catch (\ReflectionException $e) {
+            throw new ErrorException($e->getMessage());
+        }
+    }
+
+
+    protected function parseApiMethod($refClass,$refMethod){
+        $config  = $this->config;
+        $currentApp = $this->currentApp;
+        if (empty($refMethod->name)) {
+            return [];
+        }
+        $textAnnotations = ParseAnnotation::parseTextAnnotation($refMethod);
+        // 标注不解析的方法
+        if (in_array("NotParse", $textAnnotations)) {
+            return [];
+        }
+        $methodAnnotations = $this->getMethodAnnotation($refMethod);
+        $methodAnnotations = self::handleApiBaseInfo($methodAnnotations,$refClass->name,$refMethod->name,$textAnnotations,$config);
+        // 是否开启debug
+        if (
+            in_array("NotDebug", $textAnnotations) ||
+            (isset($config['notDebug']) && $config['notDebug']===true) ||
+            (isset($currentApp['notDebug']) && $currentApp['notDebug']===true)
+        ) {
+            $methodAnnotations['notDebug'] = true;
+        }
+
+        if(!empty($methodAnnotations['md'])){
+            $methodAnnotations['md'] = $this->getFieldMarkdownContent($methodAnnotations['md']);
+        }
+        if(!empty($methodAnnotations['responseSuccessMd'])){
+            $methodAnnotations['responseSuccessMd'] = $this->getFieldMarkdownContent($methodAnnotations['responseSuccessMd']);
+        }
+        if(!empty($methodAnnotations['responseErrorMd'])){
+            $methodAnnotations['responseErrorMd'] = $this->getFieldMarkdownContent($methodAnnotations['responseErrorMd']);
+        }
+
+        // 合并全局请求参数-header
+        if (
+            (
+                (!empty($config['params']) && !empty($config['params']['header']))  ||
+                (!empty($currentApp['params']) && !empty($currentApp['params']['header']))
+            ) &&
+            !in_array("NotHeaders", $textAnnotations))
+        {
+            $headers = !empty($methodAnnotations['header'])?$methodAnnotations['header']:[];
+            $methodAnnotations['header'] = $this->mergeGlobalOrAppParams($headers,'header');
+        }
+
+        // 合并全局请求参数-query
+        if (
+            (
+                (!empty($config['params']) && !empty($config['params']['query']))  ||
+                (!empty($this->currentApp['params']) && !empty($this->currentApp['params']['query']))
+            ) &&
+            !in_array("NotQuerys", $textAnnotations))
+        {
+            $querys = !empty($methodAnnotations['query'])?$methodAnnotations['query']:[];
+            $methodAnnotations['query'] = $this->mergeGlobalOrAppParams($querys,'query');
+        }
+
+        // 合并全局请求参数-body
+        if (
+            (
+                (!empty($config['params']) && !empty($config['params']['body']))  ||
+                (!empty($this->currentApp['params']) && !empty($this->currentApp['params']['body']))
+            ) &&
+            !in_array("NotParams", $textAnnotations))
+        {
+            $params = !empty($methodAnnotations['param'])?$methodAnnotations['param']:[];
+            $methodAnnotations['param'] = $this->mergeGlobalOrAppParams($params,'body');
+        }
+
+        //添加成功响应体
+        $methodAnnotations['responseSuccess'] = $this->handleApiResponseSuccess($methodAnnotations,$textAnnotations);
+        //添加异常响应体
+        $methodAnnotations['responseError'] = $this->handleApiResponseError($methodAnnotations,$textAnnotations);
+
+        // 合并全局事件after
+        if (
+            (
+                (!empty($config['debug_events']) && !empty($config['debug_events']['after']))  ||
+                (!empty($this->currentApp['debug_events']) && !empty($this->currentApp['debug_events']['after']))
+            ) &&
+            !in_array("NotEvent", $textAnnotations))
+        {
+            $debugAfterEvents = !empty($methodAnnotations['after'])?$methodAnnotations['after']:[];
+            $methodAnnotations['after'] = $this->mergeGlobalOrAppEvents($debugAfterEvents,'after');
+        }
+
+        // 合并全局事件before
+        if (
+            (
+                (!empty($config['debug_events']) && !empty($config['debug_events']['before']))  ||
+                (!empty($this->currentApp['debug_events']) && !empty($this->currentApp['debug_events']['before']))
+            ) &&
+            !in_array("NotEvent", $textAnnotations))
+        {
+            $debugBeforeEvents = !empty($methodAnnotations['before'])?$methodAnnotations['before']:[];
+            $methodAnnotations['before'] = $this->mergeGlobalOrAppEvents($debugBeforeEvents,'before');
+        }
+
+        return $methodAnnotations;
+    }
+
+    /**
+     * 获取md注解内容
+     * @param $mdAnnotations
+     * @return mixed|string
+     */
+    protected function getFieldMarkdownContent($mdAnnotations){
+        if(!empty($mdAnnotations['name'])){
+            return $mdAnnotations['name'];
+        }else if(!empty($mdAnnotations['ref'])){
+            return ParseMarkdown::getContent($this->appKey,$mdAnnotations['ref']);
+        }
+        return $mdAnnotations;
+    }
+
+    /**
+     * 获取方法的注解,并处理参数
+     * @param $refMethod
+     * @param string $refField ref时指定处理的参数
+     * @return array
+     */
+    protected function getMethodAnnotation($refMethod,$refField=""){
+        $annotations = (new ParseAnnotation($this->config))->getMethodAnnotation($refMethod);
+        // 需要处理的注解字段
+        if (!empty($refField)){
+            $handleFields =  [$refField];
+        }else{
+            $handleFields = ["header","query","param","routeParam","returned","before","after","responseSuccess","responseError"];
+        }
+        foreach ($handleFields as $field) {
+            if (!empty($annotations[$field])){
+                $annotations[$field]=$this->handleMethodParams($annotations[$field],$field);
+            }
+        }
+        return $annotations;
+    }
+
+
+    protected function mergeGlobalOrAppParams($params,$paramType='param'){
+        $config  = $this->config;
+        $currentApp = $this->currentApp;
+        $globalParams = [];
+        if (!empty($currentApp['params']) && !empty($currentApp['params'][$paramType])){
+            // 不合并global的处理方式
+            $globalParams = $currentApp['params'][$paramType];
+            // 合并global的处理方式
+            // $globalHeaders = Helper::arrayMergeAndUnique("name", $globalHeaders, $this->currentApp['params'][$paramType]);
+        }else if(!empty($config['params']) && !empty($config['params'][$paramType])){
+            $globalParams = $config['params'][$paramType];
+        }
+
+        if (!empty($params) && count($params) && count($globalParams)) {
+            return Helper::arrayMergeAndUnique("name", $globalParams, $params);
+        }
+        return $globalParams;
+    }
+
+    protected function mergeGlobalOrAppEvents($events,$eventType='after'){
+        $config  = $this->config;
+        $globalEvents = [];
+        if (!empty($this->currentApp['debug_events']) && !empty($this->currentApp['debug_events'][$eventType])){
+            $globalEvents = $this->currentApp['debug_events'][$eventType];
+        }else if(!empty($config['debug_events']) && !empty($config['debug_events'][$eventType])){
+            $globalEvents = $config['debug_events'][$eventType];
+        }
+        $mergeEvents = [];
+        foreach ($globalEvents as $item){
+            if (!empty($item['desc'])){
+                $item['desc'] = Lang::getLang($item['desc']);
+            }
+            $mergeEvents[] = $item;
+        }
+        if (!empty($events) && count($events)){
+            foreach ($events as $event) {
+                $mergeEvents[] = $event;
+            }
+        }
+        return $mergeEvents;
+    }
+
+    /**
+     * 处理接口成功响应参数
+     * @param $methodAnnotations
+     * @param $textAnnotations
+     * @return array|mixed
+     */
+    protected function handleApiResponseSuccess($methodAnnotations,$textAnnotations){
+        $returned = !empty($methodAnnotations['returned'])?$methodAnnotations['returned']:"";
+        $currentApp = $this->currentApp;
+        $config  = $this->config;
+        $mergeParams = [];
+        $paramType='success';
+        if (
+            in_array("NotResponses", $textAnnotations) ||
+            in_array("NotResponseSuccess", $textAnnotations)
+        ) {
+            // 注解了不使用全局响应
+            $mergeParams = [];
+        }else if (!empty($currentApp['responses']) && !empty($currentApp['responses'][$paramType])){
+            $mergeParams = $currentApp['params'][$paramType];
+        }else if(!empty($config['responses']) && !empty($config['responses'][$paramType])){
+            $mergeParams = $config['responses'][$paramType];
+        }
+
+        if (!empty($methodAnnotations['responseSuccess'])){
+            if (!is_int(Helper::arrayKeyFirst($methodAnnotations['responseSuccess']))){
+                $methodResponseSuccess = [$methodAnnotations['responseSuccess']];
+            }else{
+                $methodResponseSuccess = $methodAnnotations['responseSuccess'];
+            }
+            $mergeParams = Helper::arrayMergeAndUnique("name", $mergeParams,$methodResponseSuccess);
+        }
+
+        if (!empty($mergeParams) && count($mergeParams)){
+            $resData = [];
+            foreach ($mergeParams as $item) {
+                if (!empty($item['main']) && $item['main'] === true){
+                    $item['children'] = $returned;
+                }
+                $item['desc'] = Lang::getLang($item['desc']);
+                if (!empty($item['md'])){
+                    $item['md'] = ParseMarkdown::getContent($this->appKey,$item['md']);
+                }
+                $resData[]=$item;
+            }
+            return $resData;
+        }
+        return $returned;
+
+    }
+
+    /**
+     * 处理接口异常响应参数
+     * @param $methodAnnotations
+     * @param $textAnnotations
+     * @return array|mixed|void
+     */
+    protected function handleApiResponseError($methodAnnotations,$textAnnotations){
+        $config  = $this->config;
+        $currentApp = $this->currentApp;
+        $responseErrors = [];
+        $paramType = "error";
+        $mergeParams = [];
+        if (
+            in_array("NotResponses", $textAnnotations) ||
+            in_array("NotResponseError", $textAnnotations)
+        ) {
+            // 注解了不使用全局响应
+            $mergeParams = [];
+        }else if (!empty($currentApp['responses']) && !empty($currentApp['responses'][$paramType])){
+            $mergeParams = $currentApp['params'][$paramType];
+        }else if(!empty($config['responses']) && !empty($config['responses'][$paramType])){
+            $mergeParams = $config['responses'][$paramType];
+        }
+
+        if (!empty($methodAnnotations['responseError'])){
+            if (!is_int(Helper::arrayKeyFirst($methodAnnotations['responseError']))){
+                $methodResponseError = [$methodAnnotations['responseError']];
+            }else{
+                $methodResponseError = $methodAnnotations['responseError'];
+            }
+            $mergeParams = Helper::arrayMergeAndUnique("name", $mergeParams,$methodResponseError);
+        }
+
+        if (!empty($mergeParams) && count($mergeParams)){
+            $resData = [];
+            foreach ($mergeParams as $item) {
+                $item['desc'] = Lang::getLang($item['desc']);
+                if (!empty($item['md'])){
+                    $item['md'] = ParseMarkdown::getContent($this->appKey,$item['md']);
+                }
+                $resData[]=$item;
+            }
+            return $resData;
+        }
+        return [];
+    }
+
+
+
+    public static function handleApiBaseInfo($methodInfo,$className,$methodName,$textAnnotations,$config){
+        // 无标题,且有文本注释
+        if (empty($methodInfo['title']) && !empty($textAnnotations) && count($textAnnotations) > 0) {
+            $methodInfo['title'] = Lang::getLang($textAnnotations[0]);
+        }else if (!empty($methodInfo['title'])){
+            $methodInfo['title'] = Lang::getLang($methodInfo['title']);
+        }
+
+        // 默认method
+        if (!empty($methodInfo['method'])) {
+            $apiMethods = Helper::handleApiMethod($methodInfo['method']);
+            $methodInfo['method'] = count($apiMethods)==1?$apiMethods[0]:$apiMethods;
+        }else{
+            $methodInfo['method'] = !empty($config['default_method']) ? strtoupper($config['default_method']) : '*';
+        }
+
+        // 默认default_author
+        if (empty($methodInfo['author']) && !empty($config['default_author']) && !in_array("NotDefaultAuthor", $textAnnotations)) {
+            $methodInfo['author'] = $config['default_author'];
+        }
+
+        if (!empty($methodInfo['tag'])){
+            $methodInfo['tag'] = static::handleTags($methodInfo['tag']);
+        }
+        // 无url,自动生成
+        if (empty($methodInfo['url'])) {
+            $methodInfo['url'] = static::autoCreateUrl($className,$methodName,$config);
+        } else if (!empty($methodInfo['url']) && is_string($methodInfo['url']) && substr($methodInfo['url'], 0, 1) != "/") {
+            $methodInfo['url'] = "/" . $methodInfo['url'];
+        }
+        $methodInfo['name']     = $methodName;
+        $methodInfo['menuKey'] = Helper::createApiKey($className,$methodName);
+        return $methodInfo;
+    }
+
+    /**
+     * 处理方法的注解参数
+     * @param $params array 注解参数
+     * @param $field string 指定处理的参数字段名
+     * @return array
+     */
+    protected function handleMethodParams($params,$field){
+        // 不处理合并的注解字段
+        $notMergeNameFields=['before','after'];
+        $data=[];
+        if (!empty($params)){
+            // 处理单个注解为对象的参数
+            if (!is_int(Helper::arrayKeyFirst($params))){
+                $params = [$params];
+            }
+            foreach ($params as $param) {
+                $item=$this->handleAnnotationsParamItem($param,$field);
+                if (!empty($item) && is_int(Helper::arrayKeyFirst($item))){
+                    if (in_array($field,$notMergeNameFields)){
+                        $data = $item;
+                    }else{
+                        $data = Helper::arrayMergeAndUnique("name",$data,$item);
+                    }
+                }else if ($item!==false){
+                    $data[]=$item;
+                }
+            }
+        }
+        return $data;
+    }
+
+
+    /**
+     * 处理注解某一参数
+     * @param $param array 参数
+     * @param $field string 处理的字段名
+     * @return array|array[]|mixed
+     */
+    protected function handleAnnotationsParamItem($param,$field){
+        // 事件字段,不处理ref的数据过滤
+        $eventFields=['before','after'];
+        $data   = [];
+        if (!empty($param['ref'])){
+            $refParams = $this->renderRef($param['ref'],$field);
+            if (!empty($refParams[$field])){
+                if (in_array($field,$eventFields)){
+                    $data=$refParams[$field];
+                }else{
+                    $data = $this->handleRefData($param,$refParams[$field],$field);
+                }
+            }else{
+                return false;
+            }
+        }else if(!empty($param['table'])){
+            $tableParams = (new ParseModel($this->config))->getTableDocument($param['table'],[]);
+            $data = $this->handleRefData($param,$tableParams,$field);
+        } else{
+            $data = $param;
+        }
+        if (!empty($data['desc'])){
+            $data['desc'] = Lang::getLang($data['desc']);
+        }
+        if (!empty($data['md'])){
+            $data['md'] = ParseMarkdown::getContent($this->appKey,$data['md']);
+        }
+        if (!empty($data['children']) && is_array($data['children'])){
+            $childrenData = [];
+            foreach ($data['children'] as $child) {
+                $paramItem=$this->handleAnnotationsParamItem($child,$field);
+
+                if ($paramItem!==false){
+                    if (!empty($paramItem) && is_array($paramItem) && Helper::arrayKeyFirst($paramItem)===0){
+                        $childrenData = Helper::arrayMergeAndUnique("name",$childrenData,$paramItem);
+                    }else{
+                        $childrenData[] = $paramItem;
+                    }
+                }
+            }
+            $data['children'] = $childrenData;
+        }
+        if (!empty($data['type']) && $data['type'] === 'tree' ) {
+            // 类型为tree的
+            $data['children'][] = [
+                'children' => $data['children'],
+                'name'   =>!empty($data['childrenField']) ?$data['childrenField']:'children',
+                'type'   => 'array',
+                'desc'   => !empty($data['childrenDesc'])?Lang::getLang($data['childrenDesc']):"",
+            ];
+        }
+
+        // 自定义解析
+        if (!empty($this->config['parsesAnnotation'])){
+            $callback = $this->config['parsesAnnotation']($data);
+            if (!empty($callback)){
+                $data = $callback;
+            }
+        }
+        return $data;
+    }
+
+    public static function handleTags($tagStr){
+        if (!empty($tagStr)) {
+            $tagStr = Lang::getLang($tagStr);
+            $tagList = [];
+            if (is_string($tagStr) && strpos($tagStr, ',') !== false) {
+                $tagArr = explode(",", $tagStr);
+                foreach ($tagArr as $tag) {
+                    $tagList[]=Lang::getLang($tag);
+                }
+            } else {
+                $tagList = [Lang::getLang($tagStr)];
+            }
+            return $tagList;
+        }
+        return $tagStr;
+    }
+
+    /**
+     * 自动生成url
+     * @param $method
+     * @return string
+     */
+    public static function autoCreateUrl($className,$methodName,$config): string
+    {
+
+        $pathArr = explode("\\", $className);
+        $filterPathNames = !empty($config['auto_url']) && !empty($config['auto_url']['filter_keys'])?$config['auto_url']['filter_keys']:[];
+        $classUrlArr = [];
+        foreach ($pathArr as $item) {
+            if (!in_array($item, $filterPathNames)) {
+                if (!empty($config['auto_url'])){
+                    $key = $item;
+                    if (!empty($config['auto_url']['letter_rule'])){
+                        switch ($config['auto_url']['letter_rule']) {
+                            case 'lcfirst':
+                                $key = lcfirst($item);
+                                break;
+                            case 'ucfirst':
+                                $key = ucfirst($item);
+                                break;
+                            default:
+                                $key = $item;
+                        }
+                    }
+                    if (!empty($config['auto_url']['handle_key'])){
+                        $classUrlArr[] = $config['auto_url']['handle_key']($key);
+                    }else{
+                        $classUrlArr[] = $key;
+                    }
+                }else{
+                    $classUrlArr[] = $item;
+                }
+            }
+        }
+        $classUrl = implode('/', $classUrlArr);
+        $prefix = !empty($config['auto_url']) && !empty($config['auto_url']['prefix'])?$config['auto_url']['prefix']:"";
+        $url = $prefix . '/' . $classUrl . '/' . $methodName;
+        if (!empty($config['auto_url']) && !empty($config['auto_url']['custom']) && is_callable($config['auto_url']['custom'])){
+            return $config['auto_url']['custom']($className,$methodName,$url);
+        }
+        return $url;
+    }
+
+    /**
+     * ref引用
+     */
+    public function renderRef($refPath,$field): array
+    {
+        $res = ['type' => 'model'];
+        $config      = $this->config;
+        $methodName ="";
+        if (is_string($refPath)){
+            if (strpos($refPath, '\\') === false) {
+                // 引入通用注解
+                $classPath     = $config['definitions'];
+                $methodName    = $refPath;
+            }else if (class_exists($refPath)) {
+                // use类
+                $classPath  = $refPath;
+            }else if (strpos($refPath, '@') !== false){
+                // 带@指定方法
+                $pathArr   = explode("@", $refPath);
+                $classPath = $pathArr[0];
+                $methodName =  $pathArr[1];
+            }else{
+                // 直接指定方法
+                $pathArr    = explode("\\", $refPath);
+                $methodName = $pathArr[count($pathArr) - 1];
+                unset($pathArr[count($pathArr) - 1]);
+                $classPath    = implode("\\", $pathArr);
+            }
+        }else if(is_array($refPath)){
+            $classPath = $refPath[0];
+            $methodName = !empty($refPath[1])?$refPath[1]:"";
+        }else{
+            // 未知ref
+        }
+        try {
+            $modelClass =  ParseModel::getModelClass($classPath);
+            $classReflect = new \ReflectionClass($classPath);
+            if (!empty($methodName) && empty($modelClass)){
+                // 类ref引用
+                $methodName   = trim($methodName);
+                $refMethod    = $classReflect->getMethod($methodName);
+                $res = $this->getMethodAnnotation($refMethod,$field);
+                return $res;
+            }
+            // 模型解析
+            $modelParams = (new ParseModel($config))->parseModelTable($modelClass,$classReflect,$methodName);
+            return [$field=>$modelParams];
+
+        } catch (\ReflectionException $e) {
+            throw new ErrorException($e->getMessage());
+        }
+
+
+    }
+
+
+
+    public function handleRefData($annotation,$refParams, string $field): array
+    {
+
+        // 过滤field
+        if (!empty($annotation['field'])) {
+            $refParams = static::filterParamsField($refParams, $annotation['field'], 'field');
+        }
+        // 过滤withoutField
+        if (!empty($annotation['withoutField'])) {
+            $refParams = static::filterParamsField($refParams, $annotation['withoutField'], 'withoutField');
+        }
+
+        if (!empty($annotation['name'])) {
+            if (!empty($annotation['children'])) {
+                $annotation['children'] = Helper::arrayMergeAndUnique("name",$refParams,$annotation['children']);
+            }else{
+                $annotation['children'] = $refParams;
+            }
+            return $annotation;
+        }
+//        else{
+//            if (!empty($annotation[$field])) {
+//                $annotation[$field] = Helper::arrayMergeAndUnique("name",$refParams,$annotation[$field]);
+//            }
+//        }
+        return $refParams;
+    }
+    
+    
+
+
+
+    public function handleEventAnnotation($annotation,$type){
+        $config      = $this->config;
+        if (!empty($annotation->ref)){
+            if (strpos($annotation->ref, '\\') === false && !empty($config['definitions']) ) {
+                $refPath     = $config['definitions'] . '\\' . $annotation->ref;
+                $data        = $this->renderService($refPath);
+                if (!empty($data[$type])){
+                    return $data[$type];
+                }
+                return [];
+            }
+        }
+        if (!empty($annotation->value) && is_array($annotation->value)){
+            $beforeInfo = Helper::objectToArray($annotation);
+            $valueList = [];
+            foreach ($annotation->value as $valueItem){
+                $valueItemInfo = Helper::objectToArray($valueItem);
+                if ($valueItem instanceof Before){
+                    $valueItemInfo['type'] = "before";
+                }else if ($valueItem instanceof After){
+                    $valueItemInfo['type'] = "after";
+                }
+                $valueList[] = $valueItemInfo;
+            }
+            $beforeInfo['value'] = $valueList;
+            return [$beforeInfo];
+        }
+        else if (!empty($annotation->value) && is_object($annotation->value)){
+            $valueItemInfo = Helper::objectToArray($annotation->value);
+            if ($annotation->value instanceof Before){
+                $valueItemInfo['type'] = "before";
+            }else if ($annotation->value instanceof After){
+                $valueItemInfo['type'] = "after";
+            }
+            $annotation->value = [$valueItemInfo];
+            return [$annotation];
+        }else{
+            return [$annotation];
+        }
+    }
+
+
+
+    /**
+     * 过滤指定字段、或只取指定字段
+     * @param $data 参数
+     * @param $fields 指定字段
+     * @param string $type 处理类型
+     * @return array
+     */
+    public static function filterParamsField(array $data, $fields, string $type = "field"): array
+    {
+        if (!empty($fields) && is_string($fields)){
+            if (strpos($fields, ',') !== false){
+                $fieldArr = explode(',', $fields);
+            }else{
+                $fieldArr = [$fields];
+            }
+        }else if (!empty($fields) && is_array($fields)){
+            if (Helper::arrayKeyFirst($fields)=="name"){
+                $fieldArr = $fields['name'];
+            }else{
+                $fieldArr = $fields;
+            }
+        }else{
+            return $data;
+        }
+        $dataList = [];
+        foreach ($data as $item) {
+            $has = !empty($item['name']) && in_array($item['name'], $fieldArr);
+            if ($has && $type === 'field') {
+                $dataList[] = $item;
+            } else if (!($has) && $type == "withoutField") {
+                $dataList[] = $item;
+            }
+        }
+        return $dataList;
+    }
+
+
+
+}

+ 315 - 0
vendor/hg/apidoc/src/parses/ParseApiMenus.php

@@ -0,0 +1,315 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use Doctrine\Common\Annotations\AnnotationException;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+use ReflectionClass;
+use ReflectionAttribute;
+
+class ParseApiMenus
+{
+
+    protected $config = [];
+
+    //tags,当前应用/版本所有的tag
+    protected $tags = array();
+
+    //groups,当前应用/版本的分组name
+    protected $groups = array();
+
+    protected $controller_layer = "app";
+
+    protected $currentApp = [];
+
+
+    public function __construct($config)
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * 生成api接口数据
+     * @param string $appKey
+     * @return array
+     */
+    public function renderApiMenus(string $appKey): array
+    {
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $currentApp = $currentAppConfig['appConfig'];
+        $this->currentApp  = $currentApp;
+
+        $controllers = [];
+        if (!empty($currentApp['controllers']) && count($currentApp['controllers']) > 0) {
+            // 配置的控制器列表
+            $controllers = $this->getConfigControllers($currentApp['path'],$currentApp['controllers']);
+        }else if(!empty($currentApp['path']) && is_array($currentApp['path']) && count($currentApp['path'])){
+            // 读取paths的
+            foreach ($currentApp['path'] as $path) {
+                $controllersList = $this->getDirControllers($path);
+                $controllers = array_merge($controllers,$controllersList);
+            }
+        } else if(!empty($currentApp['path']) && is_string($currentApp['path'])){
+            // 默认读取path下所有的
+            $controllers = $this->getDirControllers($currentApp['path']);
+        }
+        $apiData = [];
+        if (!empty($controllers) && count($controllers) > 0) {
+            foreach ($controllers as $class) {
+                $classData = $this->parseController($class);
+                if ($classData !== false) {
+                    $apiData[] = $classData;
+                }
+            }
+        }
+        // 排序
+        $apiList = Helper::arraySortByKey($apiData);
+
+        // 接口分组
+        if (!empty($currentApp['groups'])){
+            $apiList = ParseApiMenus::mergeApiGroup($apiList,$currentApp['groups']);
+        }
+
+        $json = array(
+            "data"   => $apiList,
+            "tags"   => $this->tags,
+            "groups" => $this->groups,
+        );
+        return $json;
+    }
+
+    /**
+     * 获取生成文档的控制器列表
+     * @param string $path
+     * @return array
+     */
+    public function getConfigControllers(string $path,$appControllers): array
+    {
+        $controllers = [];
+        if (!empty($appControllers) && count($appControllers) > 0) {
+            foreach ($appControllers as $item) {
+                $classPath = $path."\\".$item;
+                if ( class_exists($classPath)) {
+                    $controllers[] = $classPath;
+                }
+            }
+        }
+        return $controllers;
+    }
+
+    /**
+     * 获取目录下的控制器列表
+     * @param string $path
+     * @return array
+     */
+    public function getDirControllers(string $path): array
+    {
+
+        if ($path) {
+            if (strpos(APIDOC_ROOT_PATH, '/') !== false) {
+                $pathStr = str_replace("\\", "/", $path);
+            } else {
+                $pathStr = $path;
+            }
+            $dir = APIDOC_ROOT_PATH . $pathStr;
+        } else {
+            $dir = APIDOC_ROOT_PATH . $this->controller_layer;
+        }
+        $controllers = [];
+        if (is_dir($dir)) {
+            $controllers = $this->scanDir($dir, $path);
+        }
+        return $controllers;
+    }
+
+
+
+    protected function scanDir($dir) {
+
+        $classList= DirAndFile::getClassList($dir);
+        $list=[];
+
+        $configFilterController = !empty($this->config['filter_controllers']) ? $this->config['filter_controllers'] : [];
+        $currentAppFilterController =  !empty($this->currentApp['filter_controllers']) ? $this->currentApp['filter_controllers'] : [];
+        $filterControllers = array_merge($configFilterController,$currentAppFilterController);
+
+        $configFilterDir = !empty($this->config['filter_dirs']) ? $this->config['filter_dirs'] : [];
+        $currentAppFilterDir =  !empty($this->currentApp['filter_dirs']) ? $this->currentApp['filter_dirs'] : [];
+        $filterDirList = array_merge($configFilterDir,$currentAppFilterDir);
+        $filterDirs=[];
+        foreach ($filterDirList as $dirItem) {
+            $dirItemPath = DirAndFile::formatPath($dirItem,"/");
+            $filterDirs[]=$dirItemPath;
+        }
+
+        foreach ($classList as $item) {
+            $classNamespace = $item['name'];
+
+            $isFilterDir = false;
+            foreach ($filterDirs as $dirItem) {
+                if (strpos($item['path'], $dirItem) !== false){
+                    $isFilterDir=true;
+                }
+            }
+            if ($isFilterDir){
+                continue;
+            }else if (
+                !in_array($classNamespace, $filterControllers) &&
+                $this->config['definitions'] != $classNamespace
+
+            ) {
+                $list[] = $classNamespace;
+            }
+        }
+
+        return $list;
+    }
+
+    public function parseController($class)
+    {
+
+        $refClass             = new ReflectionClass($class);
+        $classTextAnnotations = ParseAnnotation::parseTextAnnotation($refClass);
+        if (in_array("NotParse", $classTextAnnotations)) {
+            return false;
+        }
+
+        $data = (new ParseAnnotation($this->config))->getClassAnnotation($refClass);
+        $controllersName    = $refClass->getShortName();
+        $data['controller'] = $controllersName;
+        $data['path'] = $class;
+        if (!empty($data['group']) && !in_array($data['group'], $this->groups)) {
+            $this->groups[] = $data['group'];
+        }
+
+        if (empty($data['title'])) {
+            if (!empty($classTextAnnotations) && count($classTextAnnotations) > 0) {
+                $data['title'] = $classTextAnnotations[0];
+            } else {
+                $data['title'] = $controllersName;
+            }
+        }
+        $data['title'] = Lang::getLang($data['title']);
+        $methodList       = [];
+        $data['menuKey'] = Helper::createRandKey($data['controller']);
+        $isNotDebug = in_array("NotDebug", $classTextAnnotations);
+
+        foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
+            $methodItem = $this->parseApiMethod($refClass,$refMethod);
+            if ($methodItem===false){
+                continue;
+            }
+            if ($isNotDebug) {
+                $methodItem['notDebug'] = true;
+            }
+            $methodList[] = $methodItem;
+        }
+        $data['children'] = $methodList;
+        if (count($methodList)===0){
+            return false;
+        }
+        return $data;
+    }
+
+
+    protected function parseApiMethod($refClass,$refMethod){
+        $config               = $this->config;
+        if (empty($refMethod->name)) {
+            return false;
+        }
+
+        try {
+            $textAnnotations = ParseAnnotation::parseTextAnnotation($refMethod);
+            // 标注不解析的方法
+            if (in_array("NotParse", $textAnnotations)) {
+                return false;
+            }
+            $methodInfo = (new ParseAnnotation($this->config))->getMethodAnnotation($refMethod);
+            if (empty($methodInfo)){
+                return false;
+            }
+            $methodInfo = ParseApiDetail::handleApiBaseInfo($methodInfo,$refClass->name,$refMethod->name,$textAnnotations,$config);
+            return Helper::getArrayValuesByKeys($methodInfo,['title','method','url','author','tag','name','menuKey']);
+        }catch (AnnotationException $e) {
+            throw new ErrorException($e->getMessage());
+        }
+
+    }
+
+
+
+    /**
+     * 对象分组到tree
+     * @param $tree
+     * @param $objectData
+     * @param string $childrenField
+     * @return array
+     */
+    public static function objtctGroupByTree($tree,$objectData,$childrenField='children'){
+        $data = [];
+        foreach ($tree as $node){
+            if (!empty($node[$childrenField])){
+                $node[$childrenField] = static::objtctGroupByTree($node[$childrenField],$objectData);
+            }else if (!empty($objectData[$node['name']])){
+                $node[$childrenField] =  $objectData[$node['name']];
+            }
+            $node['menuKey'] = Helper::createRandKey( $node['name']);
+            $data[] = $node;
+        }
+        return $data;
+    }
+
+    protected static function getAppGroupNames($groups){
+        $groupNames = [];
+        foreach ($groups as $item) {
+            if (!empty($item['name'])){
+                $groupNames[]=$item['name'];
+            }
+            if (!empty($item['children']) && count($item['children'])){
+               $childrenNames = self::getAppGroupNames($item['children']);
+                foreach ($childrenNames as $childrenName) {
+                    $groupNames[]=$childrenName;
+               }
+            }
+        }
+        return $groupNames;
+    }
+
+    /**
+     * 合并接口到应用分组
+     * @param $apiData
+     * @param $groups
+     * @return array
+     */
+    public static function mergeApiGroup($apiData,$groups){
+        if (empty($groups) || count($apiData)<1){
+            return $apiData;
+        }
+        $groupNames = static::getAppGroupNames($groups);
+        $apiObject = [];
+        foreach ($apiData as $controller){
+            if (!empty($controller['group']) && in_array($controller['group'],$groupNames)){
+                if (!empty($apiObject[$controller['group']])){
+                    $apiObject[$controller['group']][] = $controller;
+                }else{
+                    $apiObject[$controller['group']] = [$controller];
+                }
+            }else{
+                if (!empty($apiObject['notGroup'])){
+                    $apiObject['notGroup'][] = $controller;
+                }else{
+                    $apiObject['notGroup'] = [$controller];
+                }
+            }
+        }
+        if (!empty($apiObject['notGroup'])){
+            array_unshift($groups,['title'=>'未分组','name'=>'notGroup']);
+        }
+        $res = static::objtctGroupByTree($groups,$apiObject);
+        return $res;
+    }
+}

+ 100 - 0
vendor/hg/apidoc/src/parses/ParseCodeTemplate.php

@@ -0,0 +1,100 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use Doctrine\Common\Annotations\AnnotationReader;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\generator\ParseTemplate;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+
+class ParseCodeTemplate
+{
+
+    protected $config = [];
+
+    protected $currentApp = [];
+
+
+    public function __construct($config)
+    {
+
+        $this->config = $config;
+    }
+
+    public function renderCode($params)
+    {
+        $appKey = $params['appKey'];
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $currentApp = $currentAppConfig['appConfig'];
+        $this->currentApp  = $currentApp;
+
+        $codeTemplate = $params['template'];
+
+        //验证参数
+
+        //验证模板文件是否存在
+
+        //解析接口数据
+        $tplData = [];
+        if ($codeTemplate['select_mode'] == 'controller'){
+            $parseApiMenusService = new ParseApiMenus($this->config);
+            $controllers = $params['selected'];
+            if (!empty($controllers) && count($controllers) > 0) {
+                $controllerList = [];
+                foreach ($controllers as $class) {
+                    $classData = $parseApiMenusService->parseController($class);
+                    if ($classData !== false) {
+                        $controllerList[] = $classData;
+                    }
+                }
+                if (empty($codeTemplate['multiple'])){
+                    $tplData = $controllerList[0];
+                }else{
+                    $tplData = $controllerList;
+                }
+            }
+        }else{
+            // api
+            $apis = $params['selected'];
+            if (!empty($apis) && count($apis) > 0) {
+                $parseApiDetailService = new ParseApiDetail($this->config);
+                $apiList = [];
+                foreach ($apis as $key) {
+                    $apiKey = urldecode($key);
+                    $apiDetail = $parseApiDetailService->renderApiDetail($appKey,$apiKey);
+                    if ($apiDetail !== false) {
+                        $apiList[] = $apiDetail;
+                    }
+                }
+                if (empty($codeTemplate['multiple'])){
+                    $tplData = $apiList[0];
+                }else{
+                    $tplData = $apiList;
+                }
+            }
+        }
+
+
+        // 读取模板
+        $templatePath =DirAndFile::formatPath( APIDOC_ROOT_PATH . $codeTemplate['template'],"/");
+        if (is_readable($templatePath) == false) {
+            throw new ErrorException("template not found",  [
+                'template' => $template
+            ]);
+        }
+        $tplParams = [
+            'form'=>  $params['form'],
+            'data'=>$tplData
+        ];
+        $html = (new ParseTemplate())->compile($templatePath,$tplParams);
+
+
+
+        return $html;
+    }
+
+
+}

+ 139 - 0
vendor/hg/apidoc/src/parses/ParseMarkdown.php

@@ -0,0 +1,139 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+
+class ParseMarkdown
+{
+    protected $config = [];
+
+    public function __construct($config)
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * 获取md文档菜单
+     * @return array
+     */
+    public function getDocsMenu($appKey,string $lang): array
+    {
+        $config  = $this->config;
+        $docData = [];
+        if (!empty($config['docs']) && count($config['docs']) > 0) {
+            $docData = $this->handleDocsMenuData($config['docs'],$appKey,$lang);
+        }
+        return $docData;
+    }
+
+    /**
+     * 处理md文档菜单数据
+     * @param array $menus
+     * @return array
+     */
+    protected function handleDocsMenuData(array $menus,$appKey,string $lang): array
+    {
+        $list = [];
+        foreach ($menus as $item) {
+            $item['title']     = Lang::getLang($item['title']);
+            if (!empty($item['appKey']) && $item['appKey'] != $appKey){
+                continue;
+            }
+
+            if (!empty($item['children']) && count($item['children']) > 0) {
+                $item['children']    = $this->handleDocsMenuData($item['children'],$appKey,$lang);
+                $item['menuKey'] = Helper::createRandKey("md_group");
+            } else {
+                $filePath    = static::getFilePath($appKey,$item['path'],$lang);
+                if (!file_exists($filePath['filePath'])) {
+                    continue;
+                }
+
+                if(!empty($item['path'])){
+                    $item['path'] = Helper::replaceTemplate($item['path'],['lang'=>$lang]);
+                }
+                $item['type']     = 'md';
+                $item['menuKey'] = Helper::createApiKey($item['path']);
+            }
+            $list[]           = $item;
+        }
+        return $list;
+    }
+
+    public static function getFilePath(string $appKey, string $path,$lang=""){
+        if (!empty($appKey)){
+            $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+            $currentApps = $currentAppConfig['apps'];
+            $fullPath      = Helper::replaceCurrentAppTemplate($path, $currentApps);
+        }else{
+            $fullPath = $path;
+        }
+        $fullPath = Helper::replaceTemplate($fullPath,[
+            'lang'=>$lang
+        ]);
+
+        if (strpos($fullPath, '#') !== false) {
+            $mdPathArr = explode("#", $fullPath);
+            $mdPath=$mdPathArr[0];
+            $mdAnchor =$mdPathArr[1];
+        } else {
+            $mdPath = $fullPath;
+            $mdAnchor="";
+        }
+        $fileSuffix = "";
+        if (strpos($fullPath, '.md') === false) {
+            $fileSuffix = ".md";
+        }
+        $filePath    = APIDOC_ROOT_PATH . $mdPath . $fileSuffix;
+        return [
+            'filePath'=>$filePath,
+            'anchor'=>$mdAnchor
+        ];
+    }
+
+
+    /**
+     * 获取md文档内容
+     * @param string $appKey
+     * @param string $path
+     * @return string
+     */
+    public static function getContent(string $appKey, string $path,$lang="")
+    {
+        $filePathArr    = static::getFilePath($appKey,$path,$lang);
+        $mdAnchor = $filePathArr['anchor'];
+        $filePath = $filePathArr['filePath'];
+        if (!file_exists($filePath)) {
+            return $path;
+        }
+        $contents    = DirAndFile::getFileContent($filePath);
+        // 获取指定h2标签内容
+        if (!empty($mdAnchor)){
+            if (strpos($contents, '## ') !== false) {
+                $contentArr = explode("\r\n", $contents);
+                $contentText = "";
+                foreach ($contentArr as $line){
+                    $contentText.="\r\n".trim($line);
+                }
+                $contentArr = explode("\r\n## ", $contentText);
+                $content="";
+                foreach ($contentArr as $item){
+                    $itemArr = explode("\r\n", $item);
+                    if (!empty($itemArr) && $itemArr[0] && $mdAnchor===$itemArr[0]){
+                        $content = str_replace($itemArr[0]."\r\n", '', $item);
+                        break;
+                    }
+                }
+                return $content;
+            }
+        }
+        return $contents;
+    }
+
+
+}

+ 163 - 0
vendor/hg/apidoc/src/parses/ParseModel.php

@@ -0,0 +1,163 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use Doctrine\Common\Annotations\Reader;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\annotation\Field;
+use hg\apidoc\annotation\WithoutField;
+use hg\apidoc\annotation\AddField;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+
+
+class ParseModel
+{
+    protected $config=[];
+
+    public function __construct($config=[])
+    {
+        $this->config = $config;
+    }
+
+    public function parseModelTable($model,$classReflect,$methodName=""){
+        if (!is_callable(array($model, 'getTable'))) {
+            return false;
+        }
+        $config = $this->config;
+        try {
+            // 获取所有模型属性
+            $propertys = $classReflect->getDefaultProperties();
+            $tableName = $model->getTable();
+            $configTablePrefix = !empty($config['database']) && !empty($config['database']['prefix'])?$config['database']['prefix']:"";
+            if (!empty($configTablePrefix) && strpos($tableName, $configTablePrefix) === false){
+                $tableName = $configTablePrefix.$model->getTable();
+            }
+            $table =$this->getTableDocument($tableName, $propertys);
+            if (empty($methodName)){
+                return $table;
+            }
+
+            $methodReflect    = $classReflect->getMethod($methodName);
+            $annotations = (new ParseAnnotation($config))->getMethodAnnotation($methodReflect);
+            if (!empty($annotations['field'])){
+                $table = ParseApiDetail::filterParamsField($table, $annotations['field'], 'field');
+            }
+            if (!empty($annotations['withoutField'])){
+                $table = ParseApiDetail::filterParamsField($table, $annotations['withoutField'], 'withoutField');
+            }
+            if (!empty($annotations['addField'])){
+                $addFieldData = [];
+                if (is_int(Helper::arrayKeyFirst($annotations['addField']))){
+                    $addFieldData = $annotations['addField'];
+                }else{
+                    $addFieldData = [$annotations['addField']];
+                }
+                $addFieldList = [];
+                $parseApiDetail = new ParseApiDetail($config);
+                $field = 'param';
+                foreach ($addFieldData as $fieldItem) {
+                    if (!empty($fieldItem['ref'])){
+                        $refParams = $parseApiDetail->renderRef($fieldItem['ref'],$field);
+                        if (!empty($refParams[$field])){
+                            $fieldItem = $parseApiDetail->handleRefData($fieldItem,$refParams[$field],$field);
+                        }
+                    }
+                    if (!empty($fieldItem['md'])){
+                        $fieldItem['md'] = ParseMarkdown::getContent("",$fieldItem['md']);
+                    }
+                    // 自定义解析
+                    if (!empty($config['parsesAnnotation'])){
+                        $callback = $config['parsesAnnotation']($fieldItem);
+                        if (!empty($callback)){
+                            $fieldItem = $callback;
+                        }
+                    }
+                    $addFieldList[]=$fieldItem;
+                }
+                $table = Helper::arrayMergeAndUnique("name",$table,$addFieldList);
+            }
+            return $table;
+        } catch (\ReflectionException $e) {
+            throw new ErrorException('Class '.get_class($model).' '.$e->getMessage());
+        }
+
+    }
+
+    /**
+     * 获取模型实例
+     * @param $method
+     * @return mixed|null
+     */
+    public static function getModelClass($namespaceName)
+    {
+        if (!empty($namespaceName) && class_exists($namespaceName)) {
+            $modelInstance = new $namespaceName();
+            if (is_callable(array($modelInstance, 'getTable'))) {
+                return $modelInstance;
+            }
+        }
+        return null;
+    }
+
+
+
+
+    /**
+     * 获取模型注解数据
+     * @param $tableName
+     * @param $propertys
+     * @return array
+     */
+    public function getTableDocument($tableName,array $propertys):array
+    {
+        $config = $this->config;
+        $fieldComment = [];
+        if (empty($config['database_query_function'])){
+            throw new ErrorException("not datatable_query_function config");
+        }
+        $tableColumns = $config['database_query_function']("SHOW FULL COLUMNS FROM " . $tableName);
+        foreach ($tableColumns as $columns) {
+            $columns = Helper::objectToArray($columns);
+            $name = $columns['Field'];
+            $desc = $columns['Comment'];
+            $mock="";
+            $md = "";
+            if (isset($propertys['convertNameToCamel']) && $propertys['convertNameToCamel'] === true) {
+                $name = Helper::camel($name);
+            }
+            if (!empty($desc)) {
+                // 存在字段注释
+                $desc = Lang::getLang($desc);
+                if (strpos($desc, 'mock(') !== false){
+                    // 存在mock
+                    preg_match('#mock\((.*)\)#s', $desc, $mocks);
+                    if (!empty($mocks[1])) {
+                        $mock = $mocks[1];
+                        $desc = str_replace($mocks[0],"",$desc);
+                    }
+                }
+                if (strpos($desc, 'md="') !== false){
+                    // 存在md
+                    preg_match('#md="(.*)"#s', $desc, $mdRefs);
+                    if (!empty($mdRefs[1])) {
+                        $md = ParseMarkdown::getContent("",$mdRefs[1]);
+                        $desc = str_replace($mdRefs[0],"",$desc);
+                    }
+                }
+            }
+            $fieldComment[] = [
+                "name"    => $name,
+                "type"    => $columns['Type'],
+                "desc"    => $desc,
+                "default" => $columns['Default'],
+                "require" => $columns['Null'] != "YES",
+                "mock"=>$mock,
+                "md"=>$md,
+            ];
+        }
+        return $fieldComment;
+    }
+
+}

+ 160 - 0
vendor/hg/apidoc/src/providers/BaseService.php

@@ -0,0 +1,160 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\utils\AutoRegisterRouts;
+use hg\apidoc\utils\Cache;
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\Helper;
+
+trait BaseService
+{
+
+    static $routes = [
+        ['rule'=>'config','route'=>'getConfig'],
+        ['rule'=>'apiMenus','route'=>'getApiMenus'],
+        ['rule'=>'apiDetail','route'=>'getApiDetail'],
+        ['rule'=>'docMenus','route'=>'getMdMenus'],
+        ['rule'=>'docDetail','route'=>'getMdDetail'],
+        ['rule'=>'verifyAuth','route'=>'verifyAuth'],
+        ['rule'=>'generator','route'=>'createGenerator'],
+        ['rule'=>'cancelAllCache','route'=>'cancelAllCache'],
+        ['rule'=>'createAllCache','route'=>'createAllCache'],
+        ['rule'=>'renderCodeTemplate','route'=>'renderCodeTemplate'],
+    ];
+
+
+
+    /**
+     * 获取apidoc配置
+     * @return array 返回apidoc配置
+     */
+    abstract static function getApidocConfig();
+
+
+    /**
+     * 注册apidoc路由
+     * @param $route 路由参数
+     * @return mixed
+     */
+    abstract static function registerRoute($route);
+
+    /**
+     * 执行Sql语句
+     * @return mixed
+     */
+    abstract static function databaseQuery($sql);
+
+    /**
+     * 获取项目根目录
+     * @return string 返回项目根目录
+     */
+    abstract static function getRootPath();
+
+    /**
+     * 获取缓存目录
+     * @return string 返回项目缓存目录
+     */
+    abstract static function getRuntimePath();
+
+
+    /**
+     * 设置当前语言
+     * @param $locale 语言标识
+     * @return mixed
+     */
+    abstract static function setLang($locale);
+
+    /**
+     * 获取语言定义
+     * @param $lang
+     * @return string
+     */
+    abstract static function getLang($lang);
+
+
+    /**
+     * 处理apidoc接口响应的数据
+     * @return mixed
+     */
+    abstract static function handleResponseJson($res);
+
+    abstract static function getTablePrefix();
+
+    // 自动注册api路由
+    static public function autoRegisterRoutes($routeFun,$config=""){
+        if (empty($config)){
+            $config = self::getApidocConfig();
+        }
+        if (isset($config['auto_register_routes']) && $config['auto_register_routes']===true) {
+            $cacheKey = "autoRegisterRoutes";
+            if (!empty($config['cache']) && $config['cache']['enable']) {
+                $cacheData = (new Cache())->get($cacheKey);
+                if (!empty($cacheData)) {
+                    $autoRegisterApis = $cacheData;
+                } else {
+                    $autoRegisterApis = (new AutoRegisterRouts($config))->getAppsApis();
+                    (new Cache())->set($cacheKey, $autoRegisterApis);
+                }
+            } else {
+                $autoRegisterApis = (new AutoRegisterRouts($config))->getAppsApis();
+            }
+            $routeFun($autoRegisterApis);
+        }
+    }
+
+    public function initConfig(){
+        ! defined('APIDOC_ROOT_PATH') && define('APIDOC_ROOT_PATH', $this->getRootPath());
+        ! defined('APIDOC_STORAGE_PATH') && define('APIDOC_STORAGE_PATH', $this->getRuntimePath());
+        $config = self::getApidocConfig();
+        $config['database_query_function'] = function ($sql){
+            return self::databaseQuery($sql);
+        };
+        if (empty($config['lang_register_function'])){
+            $config['lang_register_function'] = function ($sql){
+                return self::setLang($sql);
+            };
+        }
+        if (empty($config['lang_get_function'])){
+            $config['lang_get_function'] = function ($lang){
+                return self::getLang($lang);
+            };
+        }
+        $config['handle_response_json'] = function ($res){
+            return self::handleResponseJson($res);
+        };
+        $table_prefix = self::getTablePrefix();
+        if (!empty($config['database'])){
+            if (empty($config['prefix'])){
+                $config['database']['prefix'] = $table_prefix;
+            }
+        }else{
+            $config['database']=[
+                'prefix'=>$table_prefix
+            ];
+        }
+        ConfigProvider::set($config);
+    }
+
+    /**
+     * @param null $routeFun
+     */
+    static public function registerApidocRoutes($routeFun=null){
+        $routes = static::$routes;
+        $controller_namespace = '\hg\apidoc\Controller@';
+        $route_prefix = "/apidoc/";
+        foreach ($routes as $item) {
+            $route = [
+                'uri'=>$route_prefix.$item['rule'],
+                'callback'=>$controller_namespace.$item['route'],
+                'route'=>$item['route'],
+            ];
+            if (!empty($routeFun)){
+                $routeFun($route);
+            }else{
+                self::registerRoute($route);
+            }
+        }
+    }
+
+}

+ 56 - 0
vendor/hg/apidoc/src/providers/CommonService.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\utils\ConfigProvider;
+
+class CommonService
+{
+    use BaseService;
+
+    static function getApidocConfig()
+    {
+        // TODO: Implement getApidocConfig() method.
+    }
+
+    static function registerRoute($route)
+    {
+        // TODO: Implement registerRoute() method.
+    }
+
+    static function databaseQuery($sql)
+    {
+        // TODO: Implement databaseQuery() method.
+    }
+
+    static function getRootPath()
+    {
+        // TODO: Implement getRootPath() method.
+    }
+
+    static function getRuntimePath()
+    {
+        // TODO: Implement getRuntimePath() method.
+    }
+
+    static function setLang($locale)
+    {
+        // TODO: Implement setLang() method.
+    }
+
+    static function getLang($lang)
+    {
+        // TODO: Implement getLang() method.
+    }
+
+    static function handleResponseJson($res)
+    {
+        // TODO: Implement handleResponseJson() method.
+    }
+
+    static function getTablePrefix()
+    {
+        // TODO: Implement getTablePrefix() method.
+    }
+
+}

+ 52 - 0
vendor/hg/apidoc/src/providers/HyperfService.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\Helper;
+use Hyperf\DbConnection\Db;
+use Hyperf\HttpServer\Router\Router;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use hg\apidoc\middleware\HyperfMiddleware;
+
+
+
+class HyperfService
+{
+
+
+    static function register(){
+        ! defined('APIDOC_ROOT_PATH') && define('APIDOC_ROOT_PATH', HyperfMiddleware::getRootPath());
+        ! defined('APIDOC_STORAGE_PATH') && define('APIDOC_STORAGE_PATH', HyperfMiddleware::getRuntimePath());
+
+        CommonService::registerApidocRoutes(function ($item){
+            Router::addRoute(['GET','POST'],$item['uri'],$item['callback'],['middleware' => [HyperfMiddleware::class]]);
+        });
+
+        // 自动注册路由
+        CommonService::autoRegisterRoutes(function ($routeData){
+            foreach ($routeData as $controller) {
+                if (count($controller['methods'])){
+                    $methods= $controller['methods'];
+                    $routeCallback = function ()use ($methods){
+                        foreach ($methods as $method) {
+                            $apiMethods = Helper::handleApiMethod($method['method']);
+                            $options = [];
+                            if (!empty($method['middleware'])){
+                                $options['middleware']= $method['middleware'];
+                            }
+                            Router::addRoute([...$apiMethods,'OPTIONS'],$method['url'], $method['controller']."@".$method['name'],$options);
+                        }
+                    };
+                    $groupOptions = [];
+                    if (!empty($controller['middleware'])){
+                        $groupOptions['middleware'] = $controller['middleware'];
+                    }
+                    Router::addGroup("",$routeCallback,$groupOptions);
+                }
+            }
+        }, HyperfMiddleware::getApidocConfig());
+    }
+}

+ 110 - 0
vendor/hg/apidoc/src/providers/LaravelService.php

@@ -0,0 +1,110 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\middleware\LaravelMiddleware;
+use hg\apidoc\utils\Helper;
+use Illuminate\Support\Facades\Lang;
+use Illuminate\Support\Facades\Request;
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\Route;
+use Exception;
+use hg\apidoc\utils\ApiCrossDomain;
+use Illuminate\Support\Facades\DB;
+
+class LaravelService extends ServiceProvider
+{
+
+    use BaseService;
+
+    public function boot()
+    {
+        $this->publishes([
+            __DIR__.'/../config.php' => config_path('apidoc.php'),
+        ]);
+    }
+
+    public function register()
+    {
+        $config = static::getApidocConfig();
+        $this->initConfig();
+        self::registerApidocRoutes();
+
+        // 自动注册路由
+        self::autoRegisterRoutes(function ($routeData){
+            foreach ($routeData as $controller) {
+                if (count($controller['methods'])){
+                    $methods= $controller['methods'];
+                    $routeCallback = function ()use ($methods){
+                        foreach ($methods as $method) {
+                            $apiMethods = Helper::handleApiMethod($method['method']);
+                            $route = Route::match([...$apiMethods,'OPTIONS'],$method['url'], "\\".$method['controller']."@".$method['name']);
+                            if (!empty($method['middleware'])){
+                                $route->middleware($method['middleware']);
+                            }
+                        }
+                    };
+                    $routeGroup = Route::prefix("");
+                    if (!empty($controller['middleware'])){
+                        $routeGroup->middleware($controller['middleware']);
+                    }
+                    $routeGroup->group($routeCallback);
+                }
+            }
+        });
+
+    }
+
+    static function getApidocConfig()
+    {
+        $config = config("apidoc");
+        if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
+            $config['auto_url']['filter_keys'] = ['App','Http','Controllers'];
+        }
+        $config['app_frame'] = "laravel";
+        return $config;
+    }
+
+    static function registerRoute($route){
+        $config = self::getApidocConfig();
+        $registerRoute =  Route::any($route['uri'], $route['callback']);
+        $registerRoute->middleware([LaravelMiddleware::class]);
+        if (!empty($config['allowCrossDomain'])) {
+            $registerRoute->middleware([ApiCrossDomain::class]);
+        }
+    }
+
+    static function databaseQuery($sql){
+        return DB::select($sql);
+    }
+
+    static function getTablePrefix(){
+        $driver = config('database.default');
+        $table_prefix=config('database.connections.'.$driver.'.prefix');
+        return $table_prefix;
+    }
+
+    static function getRootPath()
+    {
+        return base_path()."/";
+    }
+
+    static function getRuntimePath()
+    {
+        return storage_path()."/";
+    }
+
+    static function setLang($locale){
+        Lang::setLocale($locale);
+    }
+
+    static function getLang($lang){
+        return trans($lang);
+    }
+
+    static function handleResponseJson($res){
+        return $res;
+    }
+
+
+}

+ 94 - 0
vendor/hg/apidoc/src/providers/ThinkPHP5Service.php

@@ -0,0 +1,94 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\utils\Helper;
+use think\facade\App;
+use think\facade\Route;
+use think\facade\Request;
+use think\facade\Lang;
+use think\Db;
+use Exception;
+
+class ThinkPHP5Service
+{
+    use BaseService;
+
+    public function run(){
+        $this->initConfig();
+        self::registerApidocRoutes();
+        // 自动注册路由
+        self::autoRegisterRoutes(function ($routeData){
+            $appRoute = app('route');
+            $routeGroup = $appRoute->getGroup();
+            foreach ($routeData as $controller) {
+                $routeGroup = $appRoute->getGroup();
+                if (!empty($controller['middleware'])){
+                    $routeGroup->middleware($controller['middleware']);
+                }
+                if (count($controller['methods'])){
+                    foreach ($controller['methods'] as $method) {
+                        $apiMethods = Helper::handleApiMethod($method['method']);
+                        $apiMethods = implode("|",$apiMethods);
+                        $route = $routeGroup->addRule($method['url'],$method['controller']."@".$method['name'],$apiMethods);
+                        if (!empty($method['middleware'])){
+                            $route->middleware($method['middleware']);
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    static function getApidocConfig()
+    {
+        $config = config("apidoc.");
+        if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
+            $config['auto_url']['filter_keys'] = ['app','controller'];
+        }
+        $config['app_frame'] = "thinkphp5";
+        return $config;
+    }
+
+    static function registerRoute($route){
+        $config = self::getApidocConfig();
+        $registerRoute = Route::rule($route['uri'], $route['callback'],"*");
+        if (!empty($config['allowCrossDomain'])) {
+            $registerRoute->allowCrossDomain();
+        }
+    }
+
+    static function databaseQuery($sql){
+        return Db::query($sql);
+    }
+
+    static function getTablePrefix(){
+        $driver = config('database.default');
+        $table_prefix=config('database.connections.'.$driver.'.prefix');
+        return $table_prefix;
+    }
+
+    static function getRootPath()
+    {
+        return App::getRootPath();
+    }
+
+    static function getRuntimePath()
+    {
+        return App::getRuntimePath();
+    }
+
+    static function setLang($locale){
+        Lang::setLangCookieVar($locale);
+    }
+
+    static function getLang($lang){
+        return Lang::get($lang);
+    }
+
+    static function handleResponseJson($res){
+        return json($res);
+    }
+
+
+}

+ 107 - 0
vendor/hg/apidoc/src/providers/ThinkPHPService.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\middleware\ThinkPHPMiddleware;
+use hg\apidoc\utils\Helper;
+use think\facade\App;
+use think\facade\Route;
+use think\facade\Request;
+use think\facade\Lang;
+use think\facade\Db;
+
+class ThinkPHPService extends \think\Service
+{
+    use BaseService;
+
+    static function getApidocConfig()
+    {
+        $config = config("apidoc");
+        if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
+            $config['auto_url']['filter_keys'] = ['app','controller'];
+        }
+        $config['app_frame'] = "thinkphp6";
+        return $config;
+    }
+    
+    public function register()
+    {
+        $config = static::getApidocConfig();
+        $this->initConfig();
+
+        $this->registerRoutes(function () use($config){
+            //注册apidoc所需路由
+            self::registerApidocRoutes(function ($route)use ($config){
+                $registerRoute = Route::any($route['uri'], $route['callback']);
+                $registerRoute->middleware([ThinkPHPMiddleware::class]);
+                if (!empty($config['allowCrossDomain'])) {
+                    $registerRoute->allowCrossDomain();
+                }
+            });
+
+            // 自动注册路由
+            self::autoRegisterRoutes(function ($routeData){
+                $appRoute = $this->app->route;
+                $appName = $this->app->http->getName();
+                foreach ($routeData as $controller) {
+                    $routeGroup = $appRoute->getGroup();
+                    if (!empty($controller['middleware'])){
+                        $routeGroup->middleware($controller['middleware']);
+                    }
+                    if (count($controller['methods'])){
+                        foreach ($controller['methods'] as $method) {
+                            if (!empty($appName)){
+                                $method['url'] = str_replace("/".$appName,'',$method['url']);
+                            }
+                            $apiMethods = Helper::handleApiMethod($method['method']);
+                            $apiMethods = implode("|",$apiMethods);
+                            $route = $routeGroup->addRule($method['url'],$method['controller']."@".$method['name'],$apiMethods);
+                            if (!empty($method['middleware'])){
+                                $route->middleware($method['middleware']);
+                            }
+                        }
+                    }
+                }
+            });
+        });
+    }
+
+    static function registerRoute($route){
+        $registerRoute = Route::any($route['uri'], $route['callback']);
+    }
+
+    static function databaseQuery($sql){
+        return Db::query($sql);
+    }
+
+    static function getTablePrefix(){
+        $driver = config('database.default');
+        $table_prefix=config('database.connections.'.$driver.'.prefix');
+        return $table_prefix;
+    }
+
+    static function getRootPath()
+    {
+        return App::getRootPath();
+    }
+
+    static function getRuntimePath()
+    {
+        return App::getRuntimePath();
+    }
+
+    static function setLang($locale){
+        \think\facade\App::loadLangPack($locale);
+        Lang::setLangSet($locale);
+    }
+
+    static function getLang($lang){
+        return Lang::get($lang);
+    }
+
+    static function handleResponseJson($res){
+        return json($res);
+    }
+
+
+}

+ 43 - 0
vendor/hg/apidoc/src/providers/WebmanService.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\Helper;
+use Webman\Route;
+use hg\apidoc\middleware\WebmanMiddleware;
+
+class WebmanService
+{
+
+    static function register(){
+        ! defined('APIDOC_ROOT_PATH') && define('APIDOC_ROOT_PATH', WebmanMiddleware::getRootPath());
+        ! defined('APIDOC_STORAGE_PATH') && define('APIDOC_STORAGE_PATH', WebmanMiddleware::getRuntimePath());
+
+        CommonService::registerApidocRoutes(function ($item){
+            Route::any($item['uri'],$item['callback'])->middleware([WebmanMiddleware::class]);
+        });
+
+        // 自动注册路由
+        CommonService::autoRegisterRoutes(function ($routeData){
+            foreach ($routeData as $controller) {
+                if (count($controller['methods'])){
+                    $methods= $controller['methods'];
+                    $routeCallback = function ()use ($methods){
+                        foreach ($methods as $method) {
+                            $apiMethods = Helper::handleApiMethod($method['method']);
+                            $route = Route::add([...$apiMethods,'OPTIONS'],$method['url'], $method['controller']."@".$method['name']);
+                            if (!empty($method['middleware'])){
+                                $route->middleware($method['middleware']);
+                            }
+                        }
+                    };
+                    $routeGroup = Route::group("",$routeCallback);
+                    if (!empty($controller['middleware'])){
+                        $routeGroup->middleware($controller['middleware']);
+                    }
+                }
+            }
+        }, WebmanMiddleware::getApidocConfig());
+    }
+}

+ 36 - 0
vendor/hg/apidoc/src/utils/AbstractAnnotation.php

@@ -0,0 +1,36 @@
+<?php
+
+declare (strict_types=1);
+
+namespace hg\apidoc\utils;
+
+use Doctrine\Common\Annotations\Annotation;
+
+abstract class AbstractAnnotation
+{
+
+    public $name;
+
+    public function __construct(...$value)
+    {
+        $formattedValue = $this->formatParams($value);
+        foreach ($formattedValue as $key => $val) {
+            if ($key=="value" && !property_exists($this, $key)){
+                $this->name = $val;
+            }else{
+                $this->{$key} = $val;
+            }
+        }
+    }
+
+    protected function formatParams($value): array
+    {
+        if (isset($value[0])) {
+            $value = $value[0];
+        }
+        if (!is_array($value)) {
+            $value = ['name' => $value];
+        }
+        return $value;
+    }
+}

+ 22 - 0
vendor/hg/apidoc/src/utils/ApiCrossDomain.php

@@ -0,0 +1,22 @@
+<?php
+declare(strict_types = 1);
+namespace hg\apidoc\utils;
+use \Illuminate\Http\Request;
+class ApiCrossDomain
+{
+
+    public function handle (Request $request, \Closure $next){
+
+        $response = $next($request);
+        $origin = $request->server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : '';
+        $response->header('Access-Control-Allow-Origin', $origin);
+        $response->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN');
+        $response->header('Access-Control-Expose-Headers', 'Authorization, authenticated');
+        $response->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, OPTIONS');
+        $response->header('Access-Control-Allow-Credentials', 'true');
+        return $response;
+
+    }
+
+
+}

+ 153 - 0
vendor/hg/apidoc/src/utils/AutoRegisterRouts.php

@@ -0,0 +1,153 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\utils;
+
+//use Doctrine\Common\Annotations\AnnotationReader;
+use hg\apidoc\parses\ParseAnnotation;
+use hg\apidoc\utils\Helper;
+use ReflectionClass;
+use hg\apidoc\annotation\Url;
+use hg\apidoc\annotation\Method;
+use hg\apidoc\annotation\RouteMiddleware;
+use hg\apidoc\parses\ParseApiDetail;
+use hg\apidoc\parses\ParseApiMenus;
+
+class AutoRegisterRouts
+{
+
+    protected $config = [];
+
+
+    protected $filterMethods = [
+        '__construct',
+    ];
+
+    public function __construct($config)
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * 解析所有应用的api
+     * @return array
+     */
+    public function getAppsApis(){
+        $apps = Helper::getAllApps($this->config['apps']);
+        $apiList = [];
+        if (!empty($apps) && count($apps)){
+            foreach ($apps as $app) {
+               $apis = $this->getAppApis($app);
+               $apiList=array_merge($apiList,$apis);
+            }
+        }
+        return $apiList;
+
+    }
+
+    /**
+     * 生成api接口数据
+     * @param array $app
+     * @return array
+     */
+    public function getAppApis($app)
+    {
+        $controllers = [];
+        if (!empty($app['controllers']) && count($app['controllers']) > 0) {
+            // 配置的控制器列表
+            $controllers = (new ParseApiMenus($this->config))->getConfigControllers($app['path'],$app['controllers']);
+        }else if(!empty($app['path']) && is_array($app['path']) && count($app['path'])){
+            $parseApiMenus = new ParseApiMenus($this->config);
+            foreach ($app['path'] as $path) {
+                $controllersList = $parseApiMenus->getDirControllers($path);
+                $controllers = array_merge($controllers,$controllersList);
+            }
+        } else if(!empty($app['path']) && is_string($app['path'])) {
+            // 默认读取所有的
+            $controllers = (new ParseApiMenus($this->config))->getDirControllers($app['path']);
+        }
+
+        $routeData = [];
+        if (!empty($controllers) && count($controllers) > 0) {
+            foreach ($controllers as $class) {
+                $classData = $this->parseController($class);
+                if ($classData !== false) {
+                    $routeData[] = $classData;
+                }
+            }
+        }
+        return $routeData;
+    }
+
+
+    public function parseController($class)
+    {
+        $refClass             = new ReflectionClass($class);
+        $classTextAnnotations = ParseAnnotation::parseTextAnnotation($refClass);
+        if (in_array("NotParse", $classTextAnnotations)) {
+            return false;
+        }
+
+
+        $methodList = [];
+        foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
+            $methodItem = $this->parseApiMethod($refClass,$refMethod);
+            if ($methodItem===false){
+                continue;
+            }
+            $methodList[] = $methodItem;
+        }
+        if (count($methodList)===0){
+            return false;
+        }
+        $data = [
+            'name'=>$refClass->name,
+            'methods'=>$methodList,
+        ];
+        $classAnnotations = (new ParseAnnotation($this->config))->getClassAnnotation($refClass);
+        //控制器中间件
+        if (!empty($classAnnotations['routeMiddleware']) && !empty($classAnnotations['routeMiddleware'])) {
+            $data['middleware'] = $classAnnotations['routeMiddleware'];
+        }
+        return $data;
+    }
+
+
+    protected function parseApiMethod($refClass,$refMethod){
+        if (empty($refMethod->name) || in_array($refMethod->name,$this->filterMethods)) {
+            return false;
+        }
+        $config               = $this->config;
+        $textAnnotations = ParseAnnotation::parseTextAnnotation($refMethod);
+        if (in_array("NotParse", $textAnnotations)) {
+            return false;
+        }
+        $methodAnnotation = (new ParseAnnotation($config))->getMethodAnnotation($refMethod);
+
+        if (empty($methodAnnotation['method'])) {
+            $method = !empty($config['default_method']) ? strtoupper($config['default_method']) : '*';
+        }else{
+            $method = $methodAnnotation['method'];
+        }
+        if (empty($methodAnnotation['url'])) {
+            $url = ParseApiDetail::autoCreateUrl($refClass->name,$refMethod->name,$config);
+        }else{
+            $url = $methodAnnotation['url'];
+        }
+        if (!empty($url) && substr($url, 0, 1) != "/") {
+            $url = "/" . $url;
+        }
+        $data = [
+            'url'=>$url,
+            'method'=>$method,
+            'name'=>$refMethod->name,
+            'controller'=>$refClass->name,
+        ];
+        if (!empty($methodAnnotation['routeMiddleware']) && !empty($methodAnnotation['routeMiddleware'])) {
+            $data['middleware'] = $methodAnnotation['routeMiddleware'];
+        }
+        return $data;
+
+    }
+
+}

+ 298 - 0
vendor/hg/apidoc/src/utils/Cache.php

@@ -0,0 +1,298 @@
+<?php
+
+declare (strict_types = 1);
+
+namespace hg\apidoc\utils;
+
+use FilesystemIterator;
+
+/**
+ * 文件缓存类
+ */
+class Cache
+{
+    /**
+     * 缓存写入次数
+     * @var integer
+     */
+    protected $writeTimes = 0;
+
+    /**
+     * 缓存读取次数
+     * @var integer
+     */
+    protected $readTimes = 0;
+
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $options = [
+        'expire'        => 0,
+        'cache_subdir'  => true,
+        'prefix'        => '',
+        'path'          => '',
+        'hash_type'     => 'md5',
+        'data_compress' => false,
+        'serialize'     => [],
+    ];
+
+    /**
+     * 架构函数
+     * @param array $options 参数
+     */
+    public function __construct( array $options = [])
+    {
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+
+        if (empty($this->options['path'])) {
+            $this->options['path'] = APIDOC_STORAGE_PATH .'/'. 'apidoc';
+        }
+
+        if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) {
+            $this->options['path'] .= DIRECTORY_SEPARATOR;
+        }
+    }
+
+    /**
+     * 取得变量的存储文件名
+     * @access public
+     * @param string $name 缓存变量名
+     * @return string
+     */
+    public function getCacheKey(string $name): string
+    {
+        $name = $name."_".hash($this->options['hash_type'], $name);
+
+        if ($this->options['prefix']) {
+            $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name;
+        }
+
+        return $this->options['path'] . $name . '.php';
+    }
+
+    /**
+     * 序列化数据
+     * @access protected
+     * @param mixed $data 缓存数据
+     * @return string
+     */
+    protected function serialize($data): string
+    {
+        if (is_numeric($data)) {
+            return (string) $data;
+        }
+
+        $serialize = $this->options['serialize'][0] ?? "serialize";
+
+        return $serialize($data);
+    }
+
+
+    /**
+     * 反序列化数据
+     * @access protected
+     * @param string $data 缓存数据
+     * @return mixed
+     */
+    protected function unserialize($data)
+    {
+        if (is_numeric($data)) {
+            return $data;
+        }
+
+        $unserialize = $this->options['serialize'][1] ?? "unserialize";
+
+        return $unserialize($data);
+    }
+
+
+    /**
+     * 获取有效期
+     * @access protected
+     * @param integer|DateTimeInterface|DateInterval $expire 有效期
+     * @return int
+     */
+    protected function getExpireTime($expire): int
+    {
+        if ($expire instanceof DateTimeInterface) {
+            $expire = $expire->getTimestamp() - time();
+        } elseif ($expire instanceof DateInterval) {
+            $expire = DateTime::createFromFormat('U', (string) time())
+                    ->add($expire)
+                    ->format('U') - time();
+        }
+
+        return (int) $expire;
+    }
+
+    /**
+     * 获取缓存数据
+     * @param string $name 缓存标识名
+     * @return array|null
+     */
+    protected function getRaw(string $name)
+    {
+        $filename = $this->getCacheKey($name);
+
+        if (!is_file($filename)) {
+            return;
+        }
+
+        $content = @file_get_contents($filename);
+
+        if (false !== $content) {
+            $expire = (int) substr($content, 8, 12);
+            $createTime = filemtime($filename);
+            if (0 != $expire && time() - $expire > $createTime) {
+                //缓存过期删除缓存文件
+                DirAndFile::unlink($item->getPathname());
+                return;
+            }
+
+            $content = substr($content, 32);
+
+            if ($this->options['data_compress'] && function_exists('gzcompress')) {
+                //启用数据压缩
+                $content = gzuncompress($content);
+            }
+
+            return is_string($content) ? ['content' => $content, 'expire' => $expire,'create_time'=>$createTime] : null;
+        }
+    }
+
+    /**
+     * 判断缓存是否存在
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name): bool
+    {
+        return $this->getRaw($name) !== null;
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name    缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = null)
+    {
+        $this->readTimes++;
+
+        $raw = $this->getRaw($name);
+
+        return is_null($raw) ? $default : $this->unserialize($raw['content']);
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string        $name   缓存变量名
+     * @param mixed         $value  存储数据
+     * @param int|\DateTime $expire 有效时间 0为永久
+     * @return bool
+     */
+    public function set($name, $value, $expire = null): bool
+    {
+        $this->writeTimes++;
+
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+
+        $expire   = $this->getExpireTime($expire);
+        $filename = $this->getCacheKey($name);
+
+        $dir = dirname($filename);
+
+        if (!is_dir($dir)) {
+            try {
+                mkdir($dir, 0755, true);
+            } catch (\Exception $e) {
+                // 创建失败
+            }
+        }
+
+        $data = $this->serialize($value);
+
+        if ($this->options['data_compress'] && function_exists('gzcompress')) {
+            //数据压缩
+            $data = gzcompress($data, 3);
+        }
+
+        $data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
+        $result = file_put_contents($filename, $data);
+
+        if ($result) {
+            clearstatcache();
+            return true;
+        }
+
+        return false;
+    }
+
+
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function delete($name): bool
+    {
+        $this->writeTimes++;
+
+        return DirAndFile::unlink($this->getCacheKey($name));
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @return bool
+     */
+    public function clear(): bool
+    {
+        $this->writeTimes++;
+
+        $dirname = $this->options['path'] . $this->options['prefix'];
+
+        $this->rmdir($dirname);
+
+        return true;
+    }
+
+
+    /**
+     * 删除文件夹
+     * @param $dirname
+     * @return bool
+     */
+    private function rmdir($dirname)
+    {
+        if (!is_dir($dirname)) {
+            return false;
+        }
+
+        $items = new FilesystemIterator($dirname);
+
+        foreach ($items as $item) {
+            if ($item->isDir() && !$item->isLink()) {
+                $this->rmdir($item->getPathname());
+            } else {
+                DirAndFile::unlink($item->getPathname());
+            }
+        }
+
+        @rmdir($dirname);
+
+        return true;
+    }
+
+}

+ 95 - 0
vendor/hg/apidoc/src/utils/ConfigProvider.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace hg\apidoc\utils;
+
+use hg\apidoc\exception\ErrorException;
+
+class ConfigProvider
+{
+
+    protected static  $defaultConfig = [
+        'cache'=>[
+            'folder'=>'apidoc'
+        ]
+    ];
+    protected static $config = [];
+
+
+    public static function get($field=""){
+
+        if (!empty(static::$config)) {
+            $config = static::$config;
+        }else{
+            throw new ErrorException('ConfigProvider get error');
+        }
+
+        return Helper::getObjectFindByField($config,$field);
+    }
+
+    public static function set($config){
+        if (!(!empty($config['cache']) && !empty($config['cache']['folder']))){
+            if (!empty($config['cache'])){
+                $config['cache']['folder'] =static::$defaultConfig['cache']['folder'];
+            }
+        }
+        $config = static::handleConfig($config);
+        static::$config = $config;
+    }
+
+    public static function handleConfig($config){
+        if (!empty($config['params'])){
+            if (!empty($config['params']['header'])){
+                $config['params']['header'] = Helper::handleArrayParams($config['params']['header'],"desc",$config);
+            }
+            if (!empty($config['params']['query'])){
+                $config['params']['query'] = Helper::handleArrayParams($config['params']['query'],"desc",$config);
+            }
+            if (!empty($config['params']['body'])){
+                $config['params']['body'] = Helper::handleArrayParams($config['params']['body'],"desc",$config);
+            }
+        }
+        if (!empty($config['responses'])){
+            if (!empty($config['responses']['success'])){
+                $config['responses']['success'] = Helper::handleArrayParams($config['responses']['success'],"desc",$config);
+            }
+            if (!empty($config['responses']['error'])){
+                $config['responses']['error'] = Helper::handleArrayParams($config['responses']['error'],"desc",$config);
+            }
+        }
+        return $config;
+    }
+
+    public static function getFeConfig(){
+        $config = static::$config;
+
+        $feConfig = [
+            'title'  =>!empty($config['title'])?Lang::getLang($config['title'] ):'',
+            'desc' =>!empty($config['title'])?Lang::getLang($config['desc']):'',
+            'apps'=>!empty($config['apps'])?$config['apps']:[],
+            'cache'=>!empty($config['cache'])?$config['cache']:[],
+            'params'=>!empty($config['params'])?$config['params']:[],
+            'responses'=>!empty($config['responses'])?$config['responses']:[],
+            'generator'=>!empty($config['generator'])?$config['generator']:[],
+            'code_template'=>!empty($config['code_template'])?$config['code_template']:[],
+        ];
+        if (!empty($feConfig['apps']) && count($feConfig['apps'])){
+            // 清除apps配置中的password
+            $feConfig['apps'] = Helper::handleAppsConfig($feConfig['apps'],true);
+        }
+
+        if (!empty($feConfig['generator'])){
+            $generatorList = [];
+            $generators= Helper::handleArrayParams($feConfig['generator'],"title");
+            foreach ($generators as $item) {
+                if (!empty($item['form']) && !empty($item['form']['items']) && count($item['form']['items'])){
+                    $item['form']['items'] = Helper::handleArrayParams( $item['form']['items'],"title");
+                }
+                $generatorList[]=$item;
+            }
+            $feConfig['generator'] = $generatorList;
+        }
+        return $feConfig;
+    }
+
+
+}

+ 238 - 0
vendor/hg/apidoc/src/utils/DirAndFile.php

@@ -0,0 +1,238 @@
+<?php
+
+namespace hg\apidoc\utils;
+
+class DirAndFile
+{
+
+
+    public static function getDirTree($path){
+        $arr = [];
+        if(is_dir($path)){
+            $dir = scandir($path);
+            foreach ($dir as $value){
+                $sub_path =static::formatPath($path .'/'.$value,"/");
+                if($value == '.' || $value == '..'){
+                    continue;
+                }else if(is_dir($sub_path)){
+                    $item = [
+                        'name'=>$value,
+                        'path'=>$sub_path,
+                    ];
+                    $children = static::getDirTree($sub_path);
+                    if (count($children)){
+                        $item['children'] = $children;
+                    }
+                    $arr[] = $item;
+                }
+            }
+        }
+        return $arr;
+    }
+
+    public static function getClassList($dir){
+        if ($handle = opendir($dir)) {
+            $file_list=[];
+            while (false !== ($file = readdir($handle))) {
+                if($file=='..' || $file=='.') continue;
+                $filePath = static::formatPath($dir.'/'.$file,"/");
+                if(is_file($filePath)) {
+                    if ('php' !== pathinfo($filePath, \PATHINFO_EXTENSION)) {
+                        continue;
+                    }
+                    $classes = self::findClasses($filePath);
+                    if (!empty($classes) && count($classes)){
+                        $file_list[] = [
+                            'name'=>$classes[0],
+                            'path'=>$filePath
+                        ];
+                    }else{
+                        $file_list=[];
+                    }
+                    continue;
+                }
+                $file_list[$file] = static::getClassList($filePath);
+                foreach($file_list[$file] as $infile) {
+                    $file_list[] = $infile;
+                }
+                unset($file_list[$file]);
+            }
+            closedir($handle);
+            return $file_list;
+        }
+        return [];
+    }
+
+    public static function formatPath($path,$type="/"){
+        if ($type==="/"){
+            $path = str_replace("\\","/",$path);
+        }else{
+            $path = str_replace("/","\\",$path);
+            $path = str_replace("\\\\","\\",$path);
+            $endStr = substr($path, -1);
+            if ($endStr=='\\'){
+                $path = substr($path,0,strlen($path)-1);
+            }
+        }
+        return $path;
+    }
+
+    private static function findClasses($path)
+    {
+        $contents = file_get_contents($path);
+        $tokens = token_get_all($contents);
+
+        $nsTokens = [\T_STRING => true, \T_NS_SEPARATOR => true];
+        if (\defined('T_NAME_QUALIFIED')) {
+            $nsTokens[T_NAME_QUALIFIED] = true;
+        }
+
+        $classes = [];
+
+        $namespace = '';
+        for ($i = 0; isset($tokens[$i]); ++$i) {
+            $token = $tokens[$i];
+
+            if (!isset($token[1])) {
+                continue;
+            }
+
+            $class = '';
+
+            switch ($token[0]) {
+                case \T_NAMESPACE:
+                    $namespace = '';
+                    // If there is a namespace, extract it
+                    while (isset($tokens[++$i][1])) {
+                        if (isset($nsTokens[$tokens[$i][0]])) {
+                            $namespace .= $tokens[$i][1];
+                        }
+                    }
+                    $namespace .= '\\';
+                    break;
+                case \T_CLASS:
+                case \T_INTERFACE:
+                case \T_TRAIT:
+                    // Skip usage of ::class constant
+                    $isClassConstant = false;
+                    for ($j = $i - 1; $j > 0; --$j) {
+                        if (!isset($tokens[$j][1])) {
+                            break;
+                        }
+
+                        if (\T_DOUBLE_COLON === $tokens[$j][0]) {
+                            $isClassConstant = true;
+                            break;
+                        } elseif (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT])) {
+                            break;
+                        }
+                    }
+
+                    if ($isClassConstant) {
+                        break;
+                    }
+
+                    // Find the classname
+                    while (isset($tokens[++$i][1])) {
+                        $t = $tokens[$i];
+                        if (\T_STRING === $t[0]) {
+                            $class .= $t[1];
+                        } elseif ('' !== $class && \T_WHITESPACE === $t[0]) {
+                            break;
+                        }
+                    }
+
+                    $classes[] = ltrim($namespace.$class, '\\');
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return $classes;
+    }
+
+
+    /**
+     * 读取文件内容
+     * @param $fileName
+     * @return false|string
+     */
+    public static function getFileContent(string $fileName): string
+    {
+        $content = "";
+        if (file_exists($fileName)) {
+            $handle  = fopen($fileName, "r");
+            $content = fread($handle, filesize($fileName));
+            fclose($handle);
+        }
+        return $content;
+    }
+
+    /**
+     * 保存文件
+     * @param $path
+     * @param $str_tmp
+     * @return bool
+     */
+    public static function createFile(string $path, string $str_tmp): bool
+    {
+        $pathArr = explode("/", $path);
+        unset($pathArr[count($pathArr) - 1]);
+        $dir = implode("/", $pathArr);
+        if (!file_exists($dir)) {
+            mkdir($dir, 0775, true);
+        }
+        $fp = fopen($path, "w") or die("Unable to open file!");
+        fwrite($fp, $str_tmp); //存入内容
+        fclose($fp);
+        return true;
+    }
+
+    /**
+     * 判断文件是否存在后,删除
+     * @access private
+     * @param string $path
+     * @return bool
+     */
+    public static function unlink(string $path): bool
+    {
+        try {
+            return is_file($path) && unlink($path);
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    public static function checkFileExist(string $path)
+    {
+        try {
+            return $path;
+        } catch (\Exception $e) {
+            return $e;
+        }
+    }
+
+    public static function deleteDir($path) {
+        if (!is_dir($path)) {
+            return false;
+        }
+        $open = opendir($path);
+        if (!$open) {
+            return false;
+        }
+        while (($v = readdir($open)) !== false) {
+            if ('.' == $v || '..' == $v) {
+                continue;
+            }
+            $item = $path . '/' . $v;
+            if (is_file($item)) {
+                unlink($item);
+                continue;
+            }
+           static::deleteDir($item);
+        }
+        closedir($open);
+        return rmdir($path);
+    }
+}

+ 548 - 0
vendor/hg/apidoc/src/utils/Helper.php

@@ -0,0 +1,548 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\utils;
+
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\parses\ParseMarkdown;
+
+class Helper
+{
+    protected static $snakeCache = [];
+    /**
+     * 统一返回json格式
+     * @param int $code
+     * @param string $msg
+     * @param string $data
+     */
+    public static function showJson(int $code = 0, string $msg = "", $data = "")
+    {
+        $res = [
+            'code' => $code,
+            'message'  => $msg,
+            'data' => $data,
+        ];
+        $handle_response_json = ConfigProvider::get("handle_response_json");
+        if (!empty($handle_response_json) && is_callable($handle_response_json)){
+            return $handle_response_json($res);
+        }
+        return $res;
+    }
+
+
+
+    /**
+     * 将tree树形数据转成list数据
+     * @param array $tree tree数据
+     * @param string $childName 子节点名称
+     * @return array  转换后的list数据
+     */
+    public static function treeToList(array $tree, string $childName = 'children',string $key = "id",string $parentField = "parent")
+    {
+        $array = array();
+        foreach ($tree as $val) {
+            $array[] = $val;
+            if (isset($val[$childName])) {
+                $children = static::treeToList($val[$childName], $childName);
+                if ($children) {
+                    $newChildren = [];
+                    foreach ($children as $item) {
+                        $item[$parentField] = $val[$key];
+                        $newChildren[]      = $item;
+                    }
+                    $array = array_merge($array, $newChildren);
+                }
+            }
+        }
+        return $array;
+    }
+
+
+
+    /**
+     * 根据一组keys获取所有关联节点
+     * @param $tree
+     * @param $keys
+     */
+    public static function getTreeNodesByKeys(array $tree, array $keys, string $field = "id", string $childrenField = "children")
+    {
+        $list = static::TreeToList($tree, $childrenField, "key");
+        $data = [];
+        foreach ($keys as $k => $v) {
+            $parent = !$k ? "" : $keys[$k - 1];
+            foreach ($list as $item) {
+                if (((!empty($item['parent']) && $item['parent'] === $parent) || empty($item['parent'])) && $item[$field] == $v) {
+                    $data[] = $item;
+                    break;
+                }
+            }
+        }
+        return $data;
+
+    }
+
+    /**
+     * 替换模板变量
+     * @param $temp
+     * @param $data
+     * @return string|string[]
+     */
+    public static function replaceTemplate(string $temp, array $data):string
+    {
+        $str = $temp;
+        foreach ($data as $k => $v) {
+            $key = '${' . $k . '}';
+            if (strpos($str, $key) !== false) {
+                $str = str_replace($key, $v, $str);
+            }
+        }
+        return $str;
+    }
+
+    /**
+     * 替换当前所选应用/版本的变量
+     * @param $temp
+     * @param $currentApps
+     * @return string|string[]
+     */
+    public static function replaceCurrentAppTemplate(string $temp,array $currentApps):string
+    {
+        $str = $temp;
+        if (!empty($currentApps) && count($currentApps) > 0) {
+            $data = [];
+            for ($i = 0; $i <= 3; $i++) {
+                if (isset($currentApps[$i])) {
+                    $appItem = $currentApps[$i];
+                    foreach ($appItem as $k => $v) {
+                        $key        = 'app[' . $i . '].' . $k;
+                        $data[$key] = $v;
+                    }
+                } else {
+                    $appItem = $currentApps[0];
+                    foreach ($appItem as $k => $v) {
+                        $key        = 'app[' . $i . '].' . $k;
+                        $data[$key] = "";
+                    }
+                }
+            }
+            $str = static::replaceTemplate($str, $data);
+        }
+        return $str;
+    }
+
+    /**
+     * 根据条件获取数组中的值
+     * @param array $array
+     * @param $query
+     * @return mixed|null
+     */
+    public static function getArrayFind(array $array, $query)
+    {
+        $res = null;
+        if (is_array($array)) {
+            foreach ($array as $item) {
+                if ($query($item)) {
+                    $res = $item;
+                    break;
+                }
+            }
+        }
+        return $res;
+    }
+
+    /**
+     * 根据条件获取数组中的index
+     * @param array $array
+     * @param $query
+     * @return mixed|null
+     */
+    public static function getArrayFindIndex(array $array, $query)
+    {
+        $res = null;
+        if (is_array($array)) {
+            foreach ($array as $k=>$item) {
+                if ($query($item)) {
+                    $res = $k;
+                    break;
+                }
+            }
+        }
+        return $res;
+    }
+
+    /**
+     * 查询符合条件的数组
+     * @param array $array
+     * @param $query
+     * @return array
+     */
+    public static function getArraybyQuery(array $array, $query)
+    {
+        $res = [];
+        if (is_array($array)) {
+            foreach ($array as $item) {
+                if ($query($item)) {
+                    $res[] = $item;
+                }
+            }
+        }
+        return $res;
+    }
+
+    /**
+     * 对象转为数组
+     * @param $object
+     * @return mixed
+     */
+    public static function objectToArray($object) {
+        $object =  json_decode( json_encode($object),true);
+        return  $object;
+    }
+
+    /**
+     * 合并对象数组并根据key去重
+     * @param string $key
+     * @param mixed ...$array
+     * @return array
+     */
+    public static function arrayMergeAndUnique(string $key = "name", ...$array):array
+    {
+        $arrayByKey = [];
+        foreach ($array as $k => $arr) {
+            if (!empty($arr) && count($arr)){
+                foreach ($arr as $item) {
+                    if (!empty($item[$key])){
+                        $arrayByKey[$item[$key]] = $item;
+                    }
+                }
+            }
+        }
+        $newArray = [];
+        foreach ($arrayByKey as $item) {
+            $newArray[]=$item;
+        }
+        return $newArray;
+    }
+
+    /**
+     * 初始化当前所选的应用/版本数据
+     * @param $appKey
+     */
+    public static function getCurrentAppConfig(string $appKey,$config=""):array
+    {
+        if (empty($config)){
+            $config = ConfigProvider::get();
+        }
+        $config['apps'] = static::handleAppsConfig($config['apps'],false,$config);
+        if (!(!empty($config['apps']) && count($config['apps']) > 0)) {
+            throw new ErrorException("no config apps");
+        }
+        if (strpos($appKey, ',') !== false) {
+            $keyArr = explode(",", $appKey);
+        } else {
+            $keyArr = [$appKey];
+        }
+        $currentApps = static::getTreeNodesByKeys($config['apps'], $keyArr, 'key', 'items');
+        if (!$currentApps) {
+            throw new ErrorException("appKey error",  [
+                'appKey' => $appKey
+            ]);
+        }
+        return [
+            'appConfig'=>$currentApps[count($currentApps) - 1],
+            'apps'=>$currentApps
+        ];
+
+    }
+
+    public static function getCacheKey($type,$appKey,$lang="",$key="",$folder="apis"){
+        return $folder."/".$type."_".$appKey."_".$lang."_".$key;
+    }
+
+    /**
+     * 处理apps配置参数
+     * @param array $apps
+     * @return array
+     */
+    public static function handleAppsConfig(array $apps,$isHandlePassword=false,$config=""):array
+    {
+        $appsConfig = [];
+        foreach ($apps as $app) {
+            if (!empty($app['password']) && $isHandlePassword===true) {
+                unset($app['password']);
+                $app['hasPassword'] = true;
+            }
+            if (!empty($app['title'])){
+                $app['title'] = Lang::getLang($app['title'],$config);
+            }
+            if (!empty($app['items']) && count($app['items']) > 0) {
+                $app['items'] = static::handleAppsConfig($app['items'],$isHandlePassword);
+            }
+            if (!empty($app['groups']) && count($app['groups']) > 0){
+                $app['groups'] = static::handleGroupsConfig($app['groups']);
+            }
+            if (!empty($app['params'])){
+                if (!empty($app['params']['header']) && count($app['params']['header']) > 0){
+                    $app['params']['header'] = static::handleArrayParams($app['params']['header'],"desc");
+                }
+                if (!empty($app['params']['query']) && count($app['params']['query']) > 0){
+                    $app['params']['query'] = static::handleArrayParams($app['params']['query'],"desc");
+                }
+                if (!empty($app['params']['body']) && count($app['params']['body']) > 0){
+                    $app['params']['body'] = static::handleArrayParams($app['params']['body'],"desc");
+                }
+            }
+            $appsConfig[] = $app;
+        }
+        return $appsConfig;
+    }
+
+    public static function handleArrayParams($array,$field,$config=""){
+        $data = [];
+        if (!empty($array) && is_array($array)){
+            foreach ($array as $item){
+                $item[$field] = Lang::getLang($item[$field],$config);
+                if (!empty($item['md'])){
+                    $item['md'] = ParseMarkdown::getContent("",$item['md']);
+                }
+                $data[]=$item;
+            }
+        }
+        return $data;
+    }
+
+    public static function getAllApps(array $apps,$parentKey=""){
+        $appList = [];
+        $separ = !empty($parentKey)?',':'';
+        foreach ($apps as $app) {
+            $appKey = $parentKey.$separ.$app['key'];
+            if (!empty($app['items']) && count($app['items'])){
+               $items = static::getAllApps($app['items'],$appKey);
+               $appList = array_merge($appList,$items);
+            }else{
+                $app['appKey'] = $appKey;
+                $appList[]=$app;
+            }
+        }
+        return $appList;
+    }
+
+    /**
+     * 处理groups配置参数
+     * @param array $groups
+     * @return array
+     */
+    public static function handleGroupsConfig(array $groups):array
+    {
+        $groupConfig = [];
+        foreach ($groups as $group) {
+            if (!empty($group['title'])){
+                $group['title'] = Lang::getLang($group['title']);
+            }
+            if (!empty($group['children']) && count($group['children']) > 0) {
+                $group['children'] = static::handleAppsConfig($group['children']);
+            }
+            $groupConfig[] = $group;
+        }
+        return $groupConfig;
+    }
+
+    /**
+     * 驼峰转下划线
+     *
+     * @param  string $value
+     * @param  string $delimiter
+     * @return string
+     */
+    public static function snake(string $value, string $delimiter = '_'): string
+    {
+        $key = $value;
+
+        if (isset(static::$snakeCache[$key][$delimiter])) {
+            return static::$snakeCache[$key][$delimiter];
+        }
+
+        if (!ctype_lower($value)) {
+            $value = preg_replace('/\s+/u', '', $value);
+
+            $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
+        }
+
+        return static::$snakeCache[$key][$delimiter] = $value;
+    }
+
+    /**
+     * 下划线转驼峰(首字母小写)
+     *
+     * @param  string $value
+     * @return string
+     */
+    public static function camel(string $value): string
+    {
+        return  lcfirst(static::studly($value));
+    }
+
+
+    /**
+     * 下划线转驼峰(首字母大写)
+     *
+     * @param  string $value
+     * @return string
+     */
+    public static function studly(string $value): string
+    {
+        $value = ucwords(str_replace(['-', '_'], ' ', $value));
+        return  str_replace(' ', '', $value);
+    }
+
+    /**
+     * 字符串转小写
+     *
+     * @param  string $value
+     * @return string
+     */
+    public static function lower(string $value): string
+    {
+        return mb_strtolower($value, 'UTF-8');
+    }
+
+    /**
+     * 创建随机key
+     * @param string $prefix
+     * @return string
+     */
+    public static function createRandKey(string $prefix=""): string{
+       return uniqid($prefix);
+    }
+
+    /**
+     * 创建api的key
+     * @param string $path
+     * @param string $name
+     * @return string
+     */
+    public static function createApiKey(string $path,string $name=""): string{
+        if ($name){
+            $key = $path."@".$name;
+        }else{
+            $key = $path;
+        }
+        $res = urlencode($key);
+        return $res;
+    }
+
+
+    /**
+     * 二维数组根据key排序
+     * @param $array
+     * @param string $field
+     * @param int $order
+     * @return mixed
+     */
+    public static function arraySortByKey($array, $field="sort",$order=SORT_ASC){
+        $sorts = [];
+        foreach ($array as $key => $row) {
+            $sorts[$key]  = isset($row[$field])?$row[$field]:"";
+        }
+        array_multisort($sorts, $order,  $array);
+        return $array;
+    }
+
+
+    /**
+     * 过滤所有空格换行符
+     * @param $str
+     * @return array|string|string[]
+     */
+    public static function trimEmpty($str){
+        $search = array(" "," ","\n","\r","\t");
+        $replace = array("","","","","");
+        return str_replace($search, $replace, $str);
+    }
+
+
+    public static function getObjectFindByField(array $data,string $name = null)
+    {
+        // 无参数时获取所有
+        if (empty($name)) {
+            return $data;
+        }
+        if (false === strpos($name, '.')) {
+            $name = strtolower($name);
+            return $data[$name] ?? [];
+        }
+        $name    = explode('.', $name);
+        $name[0] = strtolower($name[0]);
+        $result  = $data;
+        // 按.拆分成多维数组进行判断
+        foreach ($name as $val) {
+            if (isset($result[$val])) {
+                $result = $result[$val];
+            } else {
+                return [];
+            }
+        }
+        return $result;
+    }
+
+
+    public static function inArrayBuyKeyword(array $arr,string $keyword):bool{
+        $is = false;
+        foreach ($arr as $item) {
+            if (strpos($item, $keyword) !== false){
+                $is=true;
+                break;
+            }
+        }
+        return $is;
+    }
+
+    /**
+     * 处理接口请求类型为数组
+     * @param $method
+     * @return array|false|string[]
+     */
+    public static function handleApiMethod($method){
+        if (is_array($method)){
+            return $method;
+        }else if (strpos($method, ',') !== false){
+            return explode(",", strtoupper($method));
+        }else {
+            return [strtoupper($method)];
+        }
+    }
+
+
+    /**
+     * 获取数组中指定keys的值为新数组
+     * @param array $array
+     * @param array $keys
+     * @return array
+     */
+    public static function getArrayValuesByKeys(array $array,array $keys){
+        $data = [];
+        foreach ($keys as $key) {
+            if (isset($array[$key])){
+                $data[$key]=$array[$key];
+            }else{
+                $data[$key]="";
+            }
+        }
+        return $data;
+    }
+    public static function arrayKeyFirst($array){
+        if (function_exists('array_key_first')) {
+            return array_key_first($array);
+        }else{
+            foreach($array as $key => $unused) {
+                return $key;
+            }
+            return NULL;
+        }
+    }
+
+
+
+
+
+}

+ 59 - 0
vendor/hg/apidoc/src/utils/Lang.php

@@ -0,0 +1,59 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\utils;
+
+use hg\apidoc\exception\ErrorException;
+
+class Lang
+{
+
+    /**
+     * 获取多语言变量值
+     * @param $string
+     * @return mixed
+     */
+    public static function getLang($string,$config="") {
+        if (!$string){
+            return $string;
+        }
+        if (empty($config)){
+            $config = ConfigProvider::get();
+        }
+        if (empty($config["lang_get_function"])){
+            return $string;
+        }
+        $langGetFunction = $config["lang_get_function"];
+        if (empty($langGetFunction)){
+            return $string;
+        }
+        if (is_string($string) && strpos($string, 'lang(') !== false) {
+            if (preg_match('#lang\((.*)\)#s', $string, $key) !== false){
+                $langKey = $key && count($key)>1 ? trim($key[1]):"";
+                if (!empty($langKey)){
+                    return $langGetFunction($langKey);
+                }
+            }
+        }
+        return $string;
+    }
+
+    /**
+     * 二维数组设置指定字段的多语言
+     * @param $array
+     * @param $field
+     * @return array
+     */
+    public static function getArrayLang($array,$field,$config=[]){
+        $data = [];
+        if (!empty($array) && is_array($array)){
+            foreach ($array as $item){
+                $item[$field] = static::getLang($item[$field],$config);
+                $data[]=$item;
+            }
+        }
+        return $data;
+    }
+
+
+}

+ 46 - 0
vendor/hg/apidoc/src/utils/Request.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace hg\apidoc\utils;
+use Exception;
+class Request
+{
+    protected $get = [];
+
+    protected $post = [];
+
+    protected $method = "GET";
+
+    public function __construct()
+    {
+        $this->get = $_GET;
+        $this->post = $_POST;
+        $this->method = !empty($_SERVER['REQUEST_METHOD'])?$_SERVER['REQUEST_METHOD']:"";
+    }
+
+    public function get(){
+        return $this->get;
+    }
+
+    public function post(){
+        return $this->post;
+    }
+
+    public function input(){
+        $input = file_get_contents('php://input');
+        $inputObj = json_decode($input);
+        return Helper::objectToArray($inputObj);
+    }
+
+    public function param(){
+        $config = ConfigProvider::get();
+        if (!empty($config['request_params'])){
+            return $config['request_params'];
+        }
+        $method = !empty($this->method)?$this->method:"GET";
+        if ($method == "GET"){
+            return $this->get;
+        }
+        return $this->input();
+    }
+
+}