quguofeng 2 years ago
parent
commit
b7e8027550
42 changed files with 3934 additions and 0 deletions
  1. 1 0
      vendor/hg/apidoc/.gitignore
  2. 21 0
      vendor/hg/apidoc/LICENSE
  3. 75 0
      vendor/hg/apidoc/README.md
  4. 44 0
      vendor/hg/apidoc/composer.json
  5. 186 0
      vendor/hg/apidoc/src/Auth.php
  6. 287 0
      vendor/hg/apidoc/src/Controller.php
  7. 36 0
      vendor/hg/apidoc/src/Service.php
  8. 530 0
      vendor/hg/apidoc/src/Utils.php
  9. 44 0
      vendor/hg/apidoc/src/annotation/AddField.php
  10. 29 0
      vendor/hg/apidoc/src/annotation/After.php
  11. 14 0
      vendor/hg/apidoc/src/annotation/Author.php
  12. 23 0
      vendor/hg/apidoc/src/annotation/Before.php
  13. 14 0
      vendor/hg/apidoc/src/annotation/ContentType.php
  14. 16 0
      vendor/hg/apidoc/src/annotation/Desc.php
  15. 57 0
      vendor/hg/apidoc/src/annotation/EventBase.php
  16. 14 0
      vendor/hg/apidoc/src/annotation/Field.php
  17. 14 0
      vendor/hg/apidoc/src/annotation/Group.php
  18. 40 0
      vendor/hg/apidoc/src/annotation/Header.php
  19. 20 0
      vendor/hg/apidoc/src/annotation/Md.php
  20. 14 0
      vendor/hg/apidoc/src/annotation/Method.php
  21. 33 0
      vendor/hg/apidoc/src/annotation/Param.php
  22. 74 0
      vendor/hg/apidoc/src/annotation/ParamBase.php
  23. 21 0
      vendor/hg/apidoc/src/annotation/ParamMd.php
  24. 14 0
      vendor/hg/apidoc/src/annotation/ParamType.php
  25. 34 0
      vendor/hg/apidoc/src/annotation/Returned.php
  26. 21 0
      vendor/hg/apidoc/src/annotation/ReturnedMd.php
  27. 17 0
      vendor/hg/apidoc/src/annotation/Route.php
  28. 77 0
      vendor/hg/apidoc/src/annotation/Rule.php
  29. 14 0
      vendor/hg/apidoc/src/annotation/Sort.php
  30. 14 0
      vendor/hg/apidoc/src/annotation/Tag.php
  31. 14 0
      vendor/hg/apidoc/src/annotation/Title.php
  32. 14 0
      vendor/hg/apidoc/src/annotation/Url.php
  33. 14 0
      vendor/hg/apidoc/src/annotation/WithoutField.php
  34. 51 0
      vendor/hg/apidoc/src/config.php
  35. 33 0
      vendor/hg/apidoc/src/exception/AuthException.php
  36. 47 0
      vendor/hg/apidoc/src/exception/ErrorException.php
  37. 356 0
      vendor/hg/apidoc/src/generator/Index.php
  38. 334 0
      vendor/hg/apidoc/src/generator/ParseTemplate.php
  39. 48 0
      vendor/hg/apidoc/src/parseApi/CacheApiData.php
  40. 863 0
      vendor/hg/apidoc/src/parseApi/ParseAnnotation.php
  41. 117 0
      vendor/hg/apidoc/src/parseApi/ParseMarkdown.php
  42. 245 0
      vendor/hg/apidoc/src/parseApi/ParseModel.php

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

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

+ 21 - 0
vendor/hg/apidoc/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 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.

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

@@ -0,0 +1,75 @@
+<p align="center">
+    <img width="120" src="https://apidoc.demo.hg-code.com/images/logo.png">
+</p>
+
+<h1 align="center">
+  ThinkPHP ApiDoc
+</h1>
+
+<div align="center">
+ 基于 ThinkPHP 的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/l/hg/apidoc"></a>
+<a href="https://github.com/HGthecode/thinkphp-apidoc"><img src="https://img.shields.io/github/issues/HGthecode/thinkphp-apidoc"></a>
+<a href="https://github.com/HGthecode/thinkphp-apidoc"><img src="https://img.shields.io/github/forks/HGthecode/thinkphp-apidoc"></a>
+
+</div>
+
+
+## 🤷‍♀️ Apidoc是什么?
+
+如今,前后端分离的开发模式以必不可少,基于ThinkPHP可以很方便的作为Api接口的开发。可是一个Api开发过程中需要快速调试,开发完成后需要给其它开发者对接等,这时一个功能全面的Api文档工具,就显得特别重要。
+
+大多数开发者可能都是通过各种工具配合来达到这一目的,其各种工具的安装和配置也是繁琐。甚至还有通过word等文本工具手写api文档的,这样的开发效率与可维护性是非常差的。
+
+综合种种Api开发中的痛点,我们专为ThinkPHP开发了Apidoc的扩展,本插件可通过简单的注解即可生成Api文档,及帮助开发者提高生产效率的在线调试、快速生成Crud、一键生成整个模块Api等,涵盖Api开发方方面面。
+
+
+## ✨特性
+
+- 开箱即用:无繁杂的配置、安装后按文档编写注释即可自动生成API文档。
+- 在线调试:在线文档可直接调试,支持全局参数、Mock调试数据、事件执行,接口调试省时省力。
+- 轻松使用:支持公共注释定义、业务逻辑层、数据表字段等引用,几句注释即可完成。
+- 安全高效:支持访问密码验证、应用/版本独立密码;支持文档缓存。
+- 多应用/多版本:可适应各种单应用、多应用、多版本的项目的Api管理。
+- Markdown文档:支持.md文件的文档展示。
+- 控制器分组:支持控制器多级分组,更精细化管理接口目录。
+- 多语言:可结合TP多语言能力,实现接口文档的语言切换。
+
+
+## 📌兼容
+
+ThinkPHP 5.1
+
+ThinkPHP 6.x
+
+## 📖使用文档
+
+[ThinkPHP ApiDoc V3.x文档](https://hg-code.gitee.io/thinkphp-apidoc/)
+
+
+## 🏆支持我们
+
+如果本项目对您有所帮助,请点个Star支持我们
+
+- [Github](https://github.com/HGthecode/thinkphp-apidoc) -> <a href="https://github.com/HGthecode/thinkphp-apidoc" target="_blank">
+  <img height="22" src="https://img.shields.io/github/stars/HGthecode/thinkphp-apidoc?style=social" class="attachment-full size-full" alt="Star me on GitHub" data-recalc-dims="1" /></a>
+- [Gitee](https://gitee.com/hg-code/thinkphp-apidoc) -> <a href="https://gitee.com/hg-code/thinkphp-apidoc/stargazers"><img src="https://gitee.com/hg-code/thinkphp-apidoc/badge/star.svg" alt="star"></a>
+
+## 💡鸣谢
+
+<a href="http://www.thinkphp.cn/" target="_blank">ThinkPHP</a>
+
+<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/thinkphp-apidoc-demo" target="_blank">ApiDoc Demo</a>
+
+

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

@@ -0,0 +1,44 @@
+{
+    "name": "hg/apidoc",
+    "description": "thinkphp API文档自动生成",
+    "type": "think-extend",
+    "keywords": [
+        "thinkphp",
+        "apidoc",
+        "api文档",
+        "接口文档",
+        "自动生成api",
+        "注释生成",
+        "php接口文档",
+        "php api文档",
+        "Markdown"
+      ],
+    "require": {
+        "php": ">=7.1.0",
+        "doctrine/annotations": "^1.6",
+        "symfony/class-loader": "~3.4.47"
+    },
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "hg-code",
+            "email": "376401263@qq.com"
+        }
+    ],
+    "autoload": {
+        "psr-4": {
+            "hg\\apidoc\\": "src/"
+        }
+    },
+    "extra": {
+        "think": {
+            "services": [
+                "hg\\apidoc\\Service"
+            ],
+            "config": {
+                "apidoc": "src/config.php"
+            }
+        }
+    },
+    "minimum-stability": "dev"
+}

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

@@ -0,0 +1,186 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc;
+
+use think\facade\Config;
+use think\facade\Request;
+use hg\apidoc\exception\AuthException;
+
+class Auth
+{
+    protected $config = [];
+
+
+    public function __construct()
+    {
+        $this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
+    }
+
+    /**
+     * 验证密码
+     * @param $password
+     * @return false|string
+     */
+    public function verifyAuth(string $password, string $appKey)
+    {
+        if (!empty($appKey)) {
+            $currentApps = (new Utils())->getCurrentApps($appKey);
+            $currentApp  = $currentApps[count($currentApps) - 1];
+            if (!empty($currentApp) && !empty($currentApp['password'])) {
+                // 应用密码
+                if (md5($currentApp['password']) === $password) {
+                    return $this->createToken($currentApp['password']);
+                }
+                throw new AuthException("password error");
+            }
+        }
+        if ($this->config['auth']['enable']) {
+            // 密码验证
+            if (md5($this->config['auth']['password']) === $password) {
+                return $this->createToken($this->config['auth']['password']);
+            }
+            throw new AuthException("password error");
+        }
+        return false;
+    }
+
+    /**
+     * 获取tokencode
+     * @param string $password
+     * @return string
+     */
+    protected function getTokenCode(string $password): string
+    {
+//        return md5(md5($password) . strtotime(date('Y-m-d', time())));
+        return md5(md5($password));
+    }
+
+
+    /**
+     * 创建token
+     * @param string $password
+     * @return string
+     */
+    public function createToken(string $password): string
+    {
+        $expire = $this->config['auth']['expire']?$this->config['auth']['expire']:86400;
+        $data = [
+            'key'=>$this->getTokenCode($password),
+            'expire'=>time()+$expire
+        ];
+        $code = json_encode($data);
+        return $this->handleToken($code, "CE");
+    }
+
+    /**
+     * 验证token
+     * @param $token
+     * @return bool
+     */
+    public function checkToken(string $token, string $password): bool
+    {
+        if (empty($password)) {
+            $password = $this->config['auth']['password'];
+        }
+        $decode = $this->handleToken($token, "DE");
+        $deData = json_decode($decode,true);
+
+        if (!empty($deData['key']) && $deData['key'] === $this->getTokenCode($password) && !empty($deData['expire']) && $deData['expire']>time()){
+            return true;
+        }
+
+
+//        if ($decode === $this->getTokenCode($password)) {
+//            return true;
+//        }
+        return false;
+    }
+
+    /**
+     * @param $request
+     * @return bool
+     */
+    public function checkAuth(string $appKey): bool
+    {
+        $config  = $this->config;
+        $request = Request::instance();
+
+        $token = $request->param("token");
+
+        if (!empty($appKey)) {
+            $currentApps = (new Utils())->getCurrentApps($appKey);
+            $currentApp  = $currentApps[count($currentApps) - 1];
+            if (!empty($currentApp) && !empty($currentApp['password'])) {
+                if (empty($token)) {
+                    throw new AuthException("token not found");
+                }
+                // 应用密码
+                if ($this->checkToken($token, $currentApp['password'])) {
+                    return true;
+                } else {
+                    throw new AuthException("token error");
+                }
+            } else if (!(!empty($config['auth']) && $config['auth']['enable'])) {
+                return true;
+            }
+        }
+        if(!empty($config['auth']) && $config['auth']['enable'] && empty($token)){
+            throw new AuthException("token not found");
+        }else if (!empty($token) && !$this->checkToken($token, "")) {
+            throw new AuthException("token error");
+        }
+        return true;
+    }
+
+    /**
+     * 处理token
+     * @param $string
+     * @param string $operation
+     * @param string $key
+     * @param int $expiry
+     * @return false|string
+     */
+    protected function handleToken(string $string, string $operation = 'DE', string $key = '', int $expiry = 0):string
+    {
+        $ckey_length   = 4;
+        $key           = md5($key ? $key : $this->config['auth']['secret_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') {
+            if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - 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));
+        }
+    }
+
+}

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

@@ -0,0 +1,287 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc;
+
+use hg\apidoc\exception\AuthException;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\parseApi\CacheApiData;
+use hg\apidoc\parseApi\ParseAnnotation;
+use hg\apidoc\parseApi\ParseMarkdown;
+use think\App;
+use think\facade\Config;
+use think\facade\Lang;
+use think\facade\Request;
+
+class Controller
+{
+    protected $app;
+
+    protected $config;
+
+    /**
+     * @var int tp版本
+     */
+    protected $tp_version;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+        $this->tp_version = substr(\think\facade\App::version(), 0, 2) == '5.'? 5: 6;
+        $config = Config::get("apidoc")?Config::get("apidoc"):Config::get("apidoc.");
+        if (!(!empty($config['apps']) && count($config['apps']))){
+            $default_app = Config::get("app.default_app")?Config::get("app.default_app"):Config::get("app.default_module");
+            $namespace = \think\facade\App::getNamespace();
+            // tp5获取 application
+            if ($this->tp_version === 5){
+                $appPath = \think\facade\App::getAppPath();
+                $appPathArr = explode("\\", $appPath);
+                for ($i = count($appPathArr)-1; $i>0 ;$i--){
+                    if ($appPathArr[$i]){
+                        $namespace = $appPathArr[$i];
+                        break;
+                    }
+                }
+            }
+            $path = $namespace.'\\'.$default_app.'\\controller';
+            if (!is_dir($path)){
+                $path =$namespace.'\\controller';
+            }
+            $defaultAppConfig = ['title'=>$default_app,'path'=>$path,'folder'=>$default_app];
+            $config['apps'] = [$defaultAppConfig];
+        }
+        // 过滤关闭的生成器
+        if (!empty($config['generator']) && count($config['generator'])){
+            $generatorList =[];
+            foreach ($config['generator'] as $item) {
+                if (!isset($item['enable']) || (isset($item['enable']) && $item['enable']===true)){
+                    $generatorList[]=$item;
+                }
+            }
+            $config['generator'] = $generatorList;
+        }
+
+
+        Config::set(['apidoc'=>$config]);
+        $this->config = $config;
+
+
+
+    }
+
+    /**
+     * 获取配置
+     * @return \think\response\Json
+     */
+    public function getConfig(){
+        $config = $this->config;
+        if (!empty($config['auth'])){
+            unset($config['auth']['password']);
+            unset($config['auth']['key']);
+        }
+        $request = Request::instance();
+        $params = $request->param();
+
+        if (!empty($params['lang'])){
+            if ($this->tp_version === 5){
+                Lang::setLangCookieVar($params['lang']);
+            }else{
+                \think\facade\App::loadLangPack($params['lang']);
+                Lang::setLangSet($params['lang']);
+            }
+
+        }
+        $config['title'] = Utils::getLang($config['title']);
+        $config['desc'] = Utils::getLang($config['desc']);
+        $config['headers'] = Utils::getArrayLang($config['headers'],"desc");
+        $config['parameters'] = Utils::getArrayLang($config['parameters'],"desc");
+        $config['responses'] = Utils::getArrayLang($config['responses'],"desc");
+
+
+        // 清除apps配置中的password
+        $config['apps'] = (new Utils())->handleAppsConfig($config['apps'],true);
+        return Utils::showJson(0,"",$config);
+    }
+
+    /**
+     * 验证密码
+     * @return false|\think\response\Json
+     * @throws \think\Exception
+     */
+    public function verifyAuth(){
+        $config = $this->config;
+
+        $request = Request::instance();
+        $params = $request->param();
+        $password = $params['password'];
+        if (empty($password)){
+            throw new AuthException( "password not found");
+        }
+        $appKey = !empty($params['appKey'])?$params['appKey']:"";
+
+        if (!$appKey && !(!empty($config['auth']) && $config['auth']['enable'])) {
+            return false;
+        }
+        try {
+            $hasAuth = (new Auth())->verifyAuth($password,$appKey);
+            $res = [
+                "token"=>$hasAuth
+            ];
+            return Utils::showJson(0,"",$res);
+        } catch (AuthException $e) {
+            return Utils::showJson($e->getCode(),$e->getMessage());
+        }
+
+    }
+
+    /**
+     * 获取文档数据
+     * @return \think\response\Json
+     */
+    public function getApidoc(){
+
+        $config = $this->config;
+        $request = Request::instance();
+        $params = $request->param();
+        $lang = "";
+
+        if (!empty($params['lang'])){
+            $lang = $params['lang'];
+            if ($this->tp_version === 5){
+                Lang::setLangCookieVar($lang);
+            }else{
+                \think\facade\App::loadLangPack($params['lang']);
+                Lang::setLangSet($params['lang']);
+            }
+
+        }
+
+        if (!empty($params['appKey'])){
+            // 获取指定应用
+            $appKey = $params['appKey'];
+        }else{
+            // 获取默认控制器
+            $default_app = $config = Config::get("app.default_app");
+            $appKey = $default_app;
+        }
+        $currentApps = (new Utils())->getCurrentApps($appKey);
+        $currentApp  = $currentApps[count($currentApps) - 1];
+
+        (new Auth())->checkAuth($appKey);
+
+        $cacheData=null;
+        if (!empty($config['cache']) && $config['cache']['enable']){
+            $cacheKey = $appKey."_".$lang;
+            $cacheData = (new CacheApiData())->get($cacheKey);
+            if ($cacheData && empty($params['reload'])){
+                $apiData = $cacheData;
+            }else{
+                // 生成数据并缓存
+                $apiData = (new ParseAnnotation())->renderApiData($appKey);
+                (new CacheApiData())->set($cacheKey,$apiData);
+            }
+        }else{
+            // 生成数据
+            $apiData = (new ParseAnnotation())->renderApiData($appKey);
+        }
+
+        // 接口分组
+        if (!empty($currentApp['groups'])){
+            $data = (new ParseAnnotation())->mergeApiGroup($apiData['data'],$currentApp['groups']);
+        }else{
+            $data = $apiData['data'];
+        }
+        $groups=!empty($currentApp['groups'])?$currentApp['groups']:[];
+        $json=[
+            'data'=>$data,
+            'app'=>$currentApp,
+            'groups'=>$groups,
+            'tags'=>$apiData['tags'],
+        ];
+
+        return Utils::showJson(0,"",$json);
+    }
+
+    public function getMdMenus(){
+        // 获取md
+        $request = Request::instance();
+        $params = $request->param();
+        $lang = "";
+        if (!empty($params['lang'])){
+            $lang = $params['lang'];
+            if ($this->tp_version === 5){
+                Lang::setLangCookieVar($params['lang']);
+            }else{
+                \think\facade\App::loadLangPack($params['lang']);
+                Lang::setLangSet($params['lang']);
+            }
+        }
+        if (!empty($params['appKey'])){
+            // 获取指定应用
+            $appKey = $params['appKey'];
+        }else{
+            // 获取默认控制器
+            $default_app = $config = Config::get("app.default_app");
+            $appKey = $default_app;
+        }
+        (new Auth())->checkAuth($appKey);
+
+        $docs = (new ParseMarkdown())->getDocsMenu($lang);
+        return Utils::showJson(0,"",$docs);
+
+    }
+
+    /**
+     * 获取md文档内容
+     * @return \think\response\Json
+     */
+    public function getMdDetail(){
+        $request = Request::instance();
+        $params = $request->param();
+        if (!empty($params['lang'])){
+            if ($this->tp_version === 5){
+                Lang::setLangCookieVar($params['lang']);
+            }else{
+                \think\facade\App::loadLangPack($params['lang']);
+                Lang::setLangSet($params['lang']);
+            }
+        }
+        try {
+            if (empty($params['path'])){
+                throw new ErrorException("mdPath not found");
+            }
+            if (empty($params['appKey'])){
+                throw new ErrorException("appkey not found");
+            }
+            $lang="";
+            if (!empty($params['lang'])){
+                $lang=$params['lang'];
+            }
+            (new Auth())->checkAuth($params['appKey']);
+            $content = (new ParseMarkdown())->getContent($params['appKey'],$params['path'],$lang);
+            $res = [
+                'content'=>$content,
+            ];
+            return Utils::showJson(0,"",$res);
+
+        } catch (ErrorException $e) {
+            return Utils::showJson($e->getCode(),$e->getMessage());
+        }
+    }
+
+
+    public function createGenerator(){
+        $request = Request::instance();
+        $params = $request->param();
+        $res = (new generator\Index())->create($params);
+        return Utils::showJson(0,"",$res);
+    }
+
+
+
+
+
+
+
+
+}

+ 36 - 0
vendor/hg/apidoc/src/Service.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace hg\apidoc;
+
+use think\facade\Config;
+use think\facade\Route;
+
+class Service extends \think\Service
+{
+
+    public function boot()
+    {
+
+        $this->registerRoutes(function (){
+            $apidocConfig = Config::get("apidoc")?Config::get("apidoc"):Config::get("apidoc.");
+            $route_prefix = 'apidoc';
+            $routes = function () {
+                $controller_namespace = '\hg\apidoc\Controller@';
+                Route::get('config'     , $controller_namespace . 'getConfig');
+                Route::get('apiData'     , $controller_namespace . 'getApidoc');
+                Route::get('mdMenus'     , $controller_namespace . 'getMdMenus');
+                Route::get('mdDetail'     , $controller_namespace . 'getMdDetail');
+                Route::post('verifyAuth'     , $controller_namespace . 'verifyAuth');
+                Route::post('generator'     , $controller_namespace . 'createGenerator');
+            };
+            if (!empty($apidocConfig['allowCrossDomain'])){
+                Route::group($route_prefix, $routes)->allowCrossDomain();
+            }else{
+                Route::group($route_prefix, $routes);
+            }
+        });
+
+    }
+
+
+}

+ 530 - 0
vendor/hg/apidoc/src/Utils.php

@@ -0,0 +1,530 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc;
+
+use hg\apidoc\exception\ErrorException;
+use think\facade\Config;
+use think\facade\Lang;
+use think\response\Json;
+
+class Utils
+{
+    protected static $snakeCache = [];
+    /**
+     * 统一返回json格式
+     * @param int $code
+     * @param string $msg
+     * @param string $data
+     * @return \think\response\Json
+     */
+    public static function showJson(int $code = 0, string $msg = "", $data = ""):Json
+    {
+        $res = [
+            'code' => $code,
+            'msg'  => $msg,
+            'data' => $data,
+        ];
+        return json($res);
+    }
+
+    /**
+     * 过滤参数字段
+     * @param $data
+     * @param $fields
+     * @param string $type
+     * @return array
+     */
+    public function filterParamsField(array $data, $fields, string $type = "field"): array
+    {
+        if ($fields && strpos($fields, ',') !== false){
+            $fieldArr = explode(',', $fields);
+        }else{
+            $fieldArr = [$fields];
+        }
+
+        $dataList = [];
+        foreach ($data as $item) {
+            if (!empty($item['name']) && in_array($item['name'], $fieldArr) && $type === 'field') {
+                $dataList[] = $item;
+            } else if (!(!empty($item['name']) && in_array($item['name'], $fieldArr)) && $type == "withoutField") {
+                $dataList[] = $item;
+            }
+        }
+        return $dataList;
+    }
+
+    /**
+     * 读取文件内容
+     * @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, 0777, true);
+        }
+        $fp = fopen($path, "w") or die("Unable to open file!");
+        fwrite($fp, $str_tmp); //存入内容
+        fclose($fp);
+        return true;
+    }
+
+    /**
+     * 删除文件
+     * @param $path
+     */
+    public static function delFile(string $path)
+    {
+        $url = iconv('utf-8', 'gbk', $path);
+        if (PATH_SEPARATOR == ':') { //linux
+            unlink($path);
+        } else {  //Windows
+            unlink($url);
+        }
+    }
+
+    /**
+     * 将tree树形数据转成list数据
+     * @param array $tree tree数据
+     * @param string $childName 子节点名称
+     * @return array  转换后的list数据
+     */
+    public 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 = $this->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 function getTreeNodesByKeys(array $tree, array $keys, string $field = "id", string $childrenField = "children")
+    {
+        $list = $this->TreeToList($tree, $childrenField, "folder");
+        $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 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 = $this->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 $name
+     * @param mixed ...$array
+     * @return array
+     */
+    public static function arrayMergeAndUnique(string $key = "name", ...$array):array
+    {
+        $newArray = [];
+        foreach ($array as $k => $arr) {
+            if ($k===0){
+                $newArray = array_merge($newArray, $arr);
+            }else if(is_array($arr)){
+                foreach ($arr as $item){
+                    $findIndex = Utils::getArrayFindIndex($newArray,function ($row)use ($key,$item){
+                        if ($item[$key] === $row[$key]){
+                            return true;
+                        }
+                        return false;
+                    });
+                    if($findIndex>-1){
+                        $data = [];
+                        foreach ($item as $itemK=>$itemV){
+                            if ( $itemV !== null){
+                                $data[$itemK]=$itemV;
+                            }
+                        }
+                        $newArray[$findIndex] = array_merge($newArray[$findIndex],$data);
+                    }else{
+                        $newArray[]=$item;
+                    }
+                }
+            }
+        }
+
+
+        return $newArray;
+
+    }
+
+    /**
+     * 初始化当前所选的应用/版本数据
+     * @param $appKey
+     */
+    public function getCurrentApps(string $appKey,$configData=""):array
+    {
+        if (!empty($configData)){
+            $config =$configData;
+        }else{
+            $config = Config::get("apidoc")?Config::get("apidoc"):Config::get("apidoc.");
+            $config['apps'] = $this->handleAppsConfig($config['apps']);
+        }
+        if (!(!empty($config['apps']) && count($config['apps']) > 0)) {
+            throw new ErrorException("no config apps", 500);
+        }
+        if (strpos($appKey, ',') !== false) {
+            $keyArr = explode(",", $appKey);
+        } else {
+            $keyArr = [$appKey];
+        }
+        $currentApps = $this->getTreeNodesByKeys($config['apps'], $keyArr, 'folder', 'items');
+        if (!$currentApps) {
+            throw new ErrorException("appKey error", 412, [
+                'appKey' => $appKey
+            ]);
+        }
+        return $currentApps;
+
+    }
+
+    /**
+     * 处理apps配置参数
+     * @param array $apps
+     * @return array
+     */
+    public function handleAppsConfig(array $apps,$isHandlePassword=false):array
+    {
+        $appsConfig = [];
+        foreach ($apps as $app) {
+            if (!empty($app['password']) && $isHandlePassword===true) {
+                unset($app['password']);
+                $app['hasPassword'] = true;
+            }
+            if (!empty($app['title'])){
+                $app['title'] = Utils::getLang($app['title']);
+            }
+            if (!empty($app['items']) && count($app['items']) > 0) {
+                $app['items'] = $this->handleAppsConfig($app['items'],$isHandlePassword);
+            }
+            if (!empty($app['groups']) && count($app['groups']) > 0){
+                $app['groups'] = $this->handleGroupsConfig($app['groups']);
+            }
+            if (!empty($app['headers']) && count($app['headers']) > 0){
+                $app['headers'] = Utils::getArrayLang($app['headers'],"desc");
+            }
+            if (!empty($app['parameters']) && count($app['parameters']) > 0){
+                $app['parameters'] = Utils::getArrayLang($app['parameters'],"desc");
+            }
+            $appsConfig[] = $app;
+        }
+        return $appsConfig;
+    }
+
+    /**
+     * 处理groups配置参数
+     * @param array $groups
+     * @return array
+     */
+    public function handleGroupsConfig(array $groups):array
+    {
+        $groupConfig = [];
+        foreach ($groups as $group) {
+            if (!empty($group['title'])){
+                $group['title'] = Utils::getLang($group['title']);
+            }
+            if (!empty($group['children']) && count($group['children']) > 0) {
+                $group['children'] = $this->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 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);
+    }
+
+    /**
+     * 获取多语言变量值
+     * @param $string
+     * @return mixed
+     */
+    public static function getLang($string) {
+        if (!$string){
+            return $string;
+        }
+        if (strpos($string, 'lang(') !== false) {
+            if (preg_match('#lang\((.*)\)#s', $string, $key) !== false){
+                $langKey = $key && count($key)>1 ? trim($key[1]):"";
+                return Lang::get($langKey);
+            }
+        }
+        return $string;
+    }
+
+    /**
+     * 二维数组设置指定字段的多语言
+     * @param $array
+     * @param $field
+     * @return array
+     */
+    public static function getArrayLang($array,$field){
+        $data = [];
+        if (!empty($array) && is_array($array)){
+            foreach ($array as $item){
+                $item[$field] = Utils::getLang($item[$field]);
+                $data[]=$item;
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 二维数组根据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]  = $row[$field];
+        }
+        array_multisort($sorts, $order,  $array);
+        return $array;
+    }
+
+
+    /**
+     * 格式化路径
+     * @param $path
+     * @param string $type
+     * @return array|string|string[]
+     */
+    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;
+    }
+
+    /**
+     * 过滤所有空格换行符
+     * @param $str
+     * @return array|string|string[]
+     */
+    public static function trimEmpty($str){
+        $search = array(" "," ","\n","\r","\t");
+        $replace = array("","","","","");
+        return str_replace($search, $replace, $str);
+    }
+
+
+
+
+
+}

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

@@ -0,0 +1,44 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 添加模型的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class AddField extends Annotation
+{
+    /**
+     * 字段名
+     * @var string
+     */
+    public $name;
+    /**
+     * 类型
+     * @var string
+     */
+    public $type = 'string';
+
+
+    /**
+     * 默认值
+     * @var string
+     */
+    public $default;
+
+    /**
+     * 描述
+     * @var string
+     */
+    public $desc;
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+}

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

@@ -0,0 +1,29 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 接口调试前置事件
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class After extends EventBase
+{
+
+
+
+    /**
+     * 事件
+     * @Enum({"setGlobalHeader", "setGlobalParam", "clearGlobalHeader", "clearGlobalParam","ajax"})
+     * @var string
+     */
+    public $event;
+
+
+
+
+}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 作者
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Author extends Annotation
+{}

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

@@ -0,0 +1,23 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 接口调试前置事件
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class Before extends EventBase
+{
+    /**
+     * 事件
+     * @Enum({"setHeader", "setGlobalHeader", "setParam", "setGlobalParam", "clearGlobalHeader", "clearGlobalParam", "clearParam","handleParam",""})
+     * @var string
+     */
+    public $event;
+
+}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 调试时请求内容
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class ContentType extends Annotation
+{}

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

@@ -0,0 +1,16 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 描述
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","CLASS"})
+ */
+class Desc extends Annotation
+{
+
+}

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

@@ -0,0 +1,57 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+abstract class EventBase extends Annotation
+{
+
+    /**
+     * key
+     * @var string
+     */
+    public $key;
+
+
+
+    /**
+     * 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
+     */
+    public $ref;
+
+    /**
+     * 设置全局参数setGlobalHeader、setGlobalParam时指定应用
+     * @var string
+     */
+    public $appKey;
+
+
+
+}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 指定获取模型的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Field extends Annotation
+{}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 分组
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"CLASS"})
+ */
+class Group extends Annotation
+{}

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

@@ -0,0 +1,40 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+/**
+ * 请求头
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Header extends ParamBase
+{
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+
+    /**
+     * 类型
+     * @var string
+     */
+    public $type;
+
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+
+    /**
+     * 描述
+     * @var string
+     */
+    public $desc;
+
+
+}

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

@@ -0,0 +1,20 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Url
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Md extends Annotation
+{
+    /**
+     * 引入md内容
+     * @var string
+     */
+    public $ref;
+}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Url
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Method extends Annotation
+{}

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

@@ -0,0 +1,33 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+/**
+ * 请求参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class Param extends ParamBase
+{
+
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require;
+    
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * mock
+     * @var string
+     */
+    public $mock;
+}

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

@@ -0,0 +1,74 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+abstract class ParamBase extends Annotation
+{
+
+    /**
+     * 类型
+     * @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;
+
+    /**
+     * 引入说明md内容
+     * @var string
+     */
+    public $mdRef;
+
+
+}

+ 21 - 0
vendor/hg/apidoc/src/annotation/ParamMd.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * md请求参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class ParamMd extends Annotation
+{
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 参数类型
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class ParamType extends Annotation
+{}

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

@@ -0,0 +1,34 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 返回参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class Returned extends ParamBase
+{
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * 是否替换全局响应体中的参数
+     * @var bool
+     */
+    public $replaceGlobal = false;
+
+}

+ 21 - 0
vendor/hg/apidoc/src/annotation/ReturnedMd.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * md返回参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class ReturnedMd extends Annotation
+{
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+}

+ 17 - 0
vendor/hg/apidoc/src/annotation/Route.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation\Enum;
+
+
+final class Route extends Rule
+{
+    /**
+     * 请求类型
+     * @Enum({"GET","POST","PUT","DELETE","PATCH","OPTIONS","HEAD"})
+     * @var string
+     */
+    public $method = "GET";
+
+}

+ 77 - 0
vendor/hg/apidoc/src/annotation/Rule.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+abstract class Rule extends Annotation
+{
+    /**
+     * @var string|array
+     */
+    public $middleware;
+
+    /**
+     * 后缀
+     * @var string
+     */
+    public $ext;
+
+    /**
+     * @var string
+     */
+    public $deny_ext;
+
+    /**
+     * @var bool
+     */
+    public $https;
+
+    /**
+     * @var string
+     */
+    public $domain;
+
+    /**
+     * @var bool
+     */
+    public $complete_match;
+
+    /**
+     * @var string|array
+     */
+    public $cache;
+
+    /**
+     * @var bool
+     */
+    public $ajax;
+
+    /**
+     * @var bool
+     */
+    public $pjax;
+
+    /**
+     * @var bool
+     */
+    public $json;
+
+    /**
+     * @var array
+     */
+    public $filter;
+
+    /**
+     * @var array
+     */
+    public $append;
+
+    public function getOptions()
+    {
+        return array_intersect_key(get_object_vars($this), array_flip([
+            'middleware', 'ext', 'deny_ext', 'https', 'domain', 'complete_match', 'cache', 'ajax', 'pjax', 'json', 'filter', 'append',
+        ]));
+    }
+
+}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 排序
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"CLASS"})
+ */
+class Sort extends Annotation
+{}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Tag
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Tag extends Annotation
+{}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 标题
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","CLASS"})
+ */
+class Title extends Annotation
+{}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Url
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Url extends Annotation
+{}

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

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 排除模型的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class WithoutField extends Annotation
+{}

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

@@ -0,0 +1,51 @@
+<?php
+return [
+    // 文档标题
+    'title'              => 'API接口文档',
+    // 文档描述
+    'desc'               => '',
+    // 默认请求类型
+    'default_method'=>'GET',
+    // 允许跨域访问
+    'allowCrossDomain'=>false,
+    // 设置可选版本
+    'apps'           => [],
+    // 自动生成url规则
+    'auto_url' => [
+        // 字母规则
+        'letter_rule' => "lcfirst",
+        // 多级路由分隔符
+        'multistage_route_separator'  =>"."
+    ],
+    // 指定公共注释定义的文件地址
+    'definitions'        => "app\common\controller\Definitions",
+    // 缓存配置
+    'cache'              => [
+        // 是否开启缓存
+        'enable' => false,
+    ],
+    // 权限认证配置
+    'auth'               => [
+        // 是否启用密码验证
+        'enable'     => false,
+        // 全局访问密码
+        'password'   => "123456",
+        // 密码加密盐
+        'secret_key' => "apidoc#hg_code",
+        // 有效期
+        'expire' => 24*60*60
+    ],
+    // 统一的请求Header
+    'headers'=>[],
+    // 统一的请求参数Parameters
+    'parameters'=>[],
+    // 统一的请求响应体
+    'responses'=>[
+        ['name'=>'code','desc'=>'代码','type'=>'int'],
+        ['name'=>'message','desc'=>'业务信息','type'=>'string'],
+        ['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object'],
+    ],
+    // md文档
+    'docs'              => [],
+
+];

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

@@ -0,0 +1,33 @@
+<?php
+
+
+namespace hg\apidoc\exception;
+
+
+use think\Exception;
+use think\exception\HttpException;
+
+class AuthException extends HttpException
+{
+
+    protected $exceptions = [
+        'password error'     => ['code' => 4001, 'msg' => '密码不正确,请重新输入'],
+        'password not found' => ['code' => 4002, 'msg' => '密码不可为空'],
+        'token error'        => ['code' => 4003, 'msg' => '不合法的Token'],
+        'token not found'    => ['code' => 4004, 'msg' => '不存在Token'],
+    ];
+
+    public function __construct(string $exceptionCode)
+    {
+        $exception = $this->getException($exceptionCode);
+        parent::__construct(401, $exception['msg'], null, [], $exception['code']);
+    }
+
+    public function getException($exceptionCode)
+    {
+        if (isset($this->exceptions[$exceptionCode])) {
+            return $this->exceptions[$exceptionCode];
+        }
+        throw new Exception('exceptionCode "' . $exceptionCode . '" Not Found');
+    }
+}

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

@@ -0,0 +1,47 @@
+<?php
+
+
+namespace hg\apidoc\exception;
+
+
+use hg\apidoc\Utils;
+use think\Exception;
+use think\exception\HttpException;
+
+class ErrorException extends HttpException
+{
+
+    protected $exceptions = [
+        'appkey not found'     => ['code' => 4005, 'msg' => '缺少必要参数appKey'],
+        'mdPath not found'     => ['code' => 4006, 'msg' => '缺少必要参数path'],
+        'appKey error'         => ['code' => 4007, 'msg' => '不存在 folder为${appKey}的apps配置'],
+        'template not found'   => ['code' => 4008, 'msg' => '${template}模板不存在'],
+        'path not found'       => ['code' => 4009, 'msg' => '${path}目录不存在'],
+        'classname error'      => ['code' => 4010, 'msg' => '${classname}文件名不合法'],
+        'no config apps'       => ['code' => 5000, 'msg' => 'apps配置不可为空'],
+        'no debug'             => ['code' => 5001, 'msg' => '请在debug模式下,使用该功能'],
+        'no config crud'       => ['code' => 5002, 'msg' => 'crud未配置'],
+        'datatable create error' => ['code' => 5003, 'msg' => '${table}数据表创建失败,请检查配置。【error】${message}。【sql】:${sql}'],
+        'file already exists' => ['code' => 5004, 'msg' => '${filepath}文件已存在'],
+        'file not exists' => ['code' => 5005, 'msg' => '${filepath}文件不存在'],
+        'datatable already exists' => ['code' => 5006, 'msg' => '数据表${table}已存在'],
+        'datatable not exists' => ['code' => 5007, 'msg' => '数据表${table}不存在'],
+        'ref file not exists' => ['code' => 5008, 'msg' => 'ref引入文件${filepath}不存在'],
+    ];
+
+    public function __construct(string $exceptionCode, int $statusCode = 412, array $data = [])
+    {
+        $exception = $this->getException($exceptionCode);
+        $msg       = Utils::replaceTemplate($exception['msg'], $data);
+        parent::__construct($statusCode, $msg, null, [], $exception['code']);
+    }
+
+    public function getException($exceptionCode)
+    {
+        if (isset($this->exceptions[$exceptionCode])) {
+            return $this->exceptions[$exceptionCode];
+        }
+        throw new Exception('exceptionCode "' . $exceptionCode . '" Not Found');
+    }
+
+}

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

@@ -0,0 +1,356 @@
+<?php
+
+namespace hg\apidoc\generator;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\generator\ParseTemplate;
+use hg\apidoc\Utils;
+use think\facade\App;
+use think\facade\Config;
+use think\facade\Db;
+use think\Db as Db5;
+use think\helper\Str;
+
+class Index
+{
+    protected $config = [];
+
+    protected $middlewares = [];
+
+    public function __construct()
+    {
+        $this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
+    }
+
+    public function create($params){
+        $appKey = $params['form']['appKey'];
+        $currentApps = (new Utils())->getCurrentApps($appKey);
+        $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 = Utils::getArrayFind($generatorItem['files'], function ($item) use ($file) {
+                    if ($file['name'] === $item['name']) {
+                        return true;
+                    }
+                    return false;
+                });
+
+                $filePath = (new Utils())->replaceCurrentAppTemplate($fileConfig['path'], $currentApps);
+                if (!empty($fileConfig['namespace'])) {
+                    $fileNamespace = (new Utils())->replaceCurrentAppTemplate($fileConfig['namespace'], $currentApps);
+                } else {
+                    $fileNamespace = $filePath;
+                }
+                $fileNamespaceEndStr = substr($fileNamespace, -1);
+                if ($fileNamespaceEndStr == '\\') {
+                    $fileNamespace = substr($fileNamespace, 0, strlen($fileNamespace) - 1);
+                }
+                $template = (new Utils())->replaceCurrentAppTemplate($fileConfig['template'], $currentApps);
+                $tplParams[$file['name']] = [
+                    'class_name' => $file['value'],
+                    'path' => $filePath,
+                    'namespace' => $fileNamespace,
+                    'template' => $template
+                ];
+
+                // 验证模板是否存在
+                $templatePath =Utils::formatPath( App::getRootPath() . $template,"/");
+                if (is_readable($templatePath) == false) {
+                    throw new ErrorException("template not found", 412, [
+                        'template' => $template
+                    ]);
+                }
+                // 验证是否已存在生成的文件
+                $fileFullPath = Utils::formatPath(App::getRootPath() . $filePath, "/");
+                $type = "folder";
+                if (strpos($fileFullPath, '.php') !== false) {
+                    // 路径为php文件,则验证文件是否存在
+                    if (is_readable($fileFullPath) == false) {
+                        throw new ErrorException("file not exists", 412, [
+                            '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", 412, [
+                            'filepath' => Utils::formatPath($filePath) . $fileName . ".php"
+                        ]);
+                    }
+                }
+                $createFiles[] = [
+                    'fileFullPath' => $fileFullPath,
+                    'template' => $template,
+                    'templatePath'=>$templatePath,
+                    'type' => $type
+                ];
+
+
+            }
+        }
+
+        $createModels = $this->checkModels($generatorItem,$tplParams);
+        return [
+            'tplParams'=>$tplParams,
+            'createFiles'=>$createFiles,
+            'createModels' =>$createModels
+        ];
+    }
+
+    /**
+     * 验证模型及表
+     * @param $generatorItem
+     * @param $tplParams
+     * @return array
+     */
+    protected function checkModels($generatorItem,$tplParams){
+        $res="";
+        $tabls = $tplParams['tables'];
+        $createModels = [];
+        $tp_version = \think\facade\App::version();
+        if (!empty($tabls) && count($tabls)){
+            foreach ($tabls as $k=>$table) {
+                $tableConfig = $generatorItem['table'];
+                $fileFullPath="";
+                if (!empty($table['model_name'])){
+                    $namespace = $tableConfig['items'][$k]['namespace'];
+                    $template = $tableConfig['items'][$k]['template'];
+                    $path = $tableConfig['items'][$k]['path'];
+
+                    // 验证模板是否存在
+                    $templatePath = Utils::formatPath(App::getRootPath() . $template,"/");
+                    if (is_readable($templatePath) == false) {
+                        throw new ErrorException("template not found", 412, [
+                            'template' => $template
+                        ]);
+                    }
+                    $tplParams['tables'][$k]['class_name'] =$table['model_name'];
+                    // 验证模型是否已存在
+                    $fileName = $table['model_name'];
+                    $fileFullPath = Utils::formatPath(App::getRootPath().$path) . "/" . $fileName . ".php";
+                    if (is_readable($fileFullPath)) {
+                        throw new ErrorException("file already exists", 412, [
+                            'filepath' => Utils::formatPath($path) . "/" . $fileName . ".php"
+                        ]);
+                    }
+                }
+                // 验证表是否存在
+                if ($table['table_name']){
+                    $driver = Config::get('database.default');
+
+                    $table_prefix=Config::get('database.connections.'.$driver.'.prefix');
+                    $table_name = $table_prefix.$table['table_name'];
+
+                    if (substr($tp_version, 0, 2) == '5.'){
+                        $isTable = Db5::query('SHOW TABLES LIKE '."'".$table_name."'");
+                    }else{
+                        $isTable = Db::query('SHOW TABLES LIKE '."'".$table_name."'");
+                    }
+                    if ($isTable){
+                        throw new ErrorException("datatable already exists", 412, [
+                            'table' => $table_name
+                        ]);
+                    }
+                }
+                $createModels[]=[
+                    'namespace'=>$namespace,
+                    'template'=>$template,
+                    'path'=>$path,
+                    'templatePath' =>$templatePath,
+                    'table'=>$table,
+                    'fileFullPath'=>$fileFullPath,
+                    'database_engine'=>!empty($tableConfig['items'][$k]['database_engine'])?$tableConfig['items'][$k]['database_engine']:''
+                ];
+            }
+        }
+        return $createModels;
+
+    }
+
+    /**
+     * 创建文件
+     * @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 = Utils::getFileContent($fileItem['fileFullPath']);
+                    $content = $pathFileContent."\r\n".$html;
+                    Utils::createFile($fileItem['fileFullPath'],$content);
+                }else{
+                    Utils::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'])){
+                    $table['database_engine'] = !empty($item['database_engine'])?$item['database_engine']:"";
+                    $res =  $this->createTable($table);
+                }
+                if (!empty($table['model_name'])){
+                    $tplParams['tables'][$k]['class_name'] =$table['model_name'];
+                    $html = (new ParseTemplate())->compile($item['templatePath'],$tplParams);
+                    Utils::createFile($item['fileFullPath'],$html);
+                }
+
+            }
+        }
+    }
+
+    /**
+     * 创建数据表
+     * @return mixed
+     */
+    protected function createTable($table){
+        $datas = $table['datas'];
+        $tp_version = \think\facade\App::version();
+        $comment= "";
+        if (!empty($table['table_comment'])){
+            $comment =$table['table_comment'];
+        }
+        $driver = Config::get('database.default');
+
+        $engine = !empty($table['database_engine'])?$table['database_engine']:'InnoDB';
+        if (substr($tp_version, 0, 2) == '5.'){
+            $charset = Config::get('database.charset');
+        }else{
+            $charset = Config::get('database.connections.'.$driver.'.charset');//获取配置内的数据库字符集
+        }
+
+        $table_prefix=Config::get('database.connections.'.$driver.'.prefix');
+        $table_name = $table_prefix.$table['table_name'];
+        $table_data = '';
+        $main_keys = '';
+        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")){
+                $table_field.=" DEFAULT '".$item['default']."'";
+            }else if (!empty($item['main_key']) && !$item['not_null']){
+                $table_field.=" 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`)";
+        }
+        $sql = "CREATE TABLE IF NOT EXISTS `$table_name` (
+        $table_data
+        $primaryKey
+        ) ENGINE=$engine DEFAULT CHARSET=$charset COMMENT='$comment' AUTO_INCREMENT=1 ;";
+
+        if (substr($tp_version, 0, 2) == '5.'){
+            Db5::startTrans();
+            try {
+                Db5::query($sql);
+                Db5::commit();
+                return true;
+            } catch (\Exception $e) {
+                Db5::rollback();
+                return $e->getMessage();
+            }
+        }else{
+            Db::startTrans();
+            try {
+                Db::query($sql);
+                Db::commit();
+                return true;
+            } catch (\Exception $e) {
+                Db::rollback();
+                throw new ErrorException("datatable create error", 412, [
+                    'table' => $table_name,
+                    'message'=>$e->getMessage(),
+                    'sql'=>$sql
+                ]);
+            }
+        }
+
+
+    }
+}

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

@@ -0,0 +1,334 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\generator;
+use hg\apidoc\Utils;
+use think\facade\App;
+
+class ParseTemplate
+{
+
+    public function compile($path,$params)
+    {
+        $filePath    =  $path;
+        $tplContent = Utils::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 ($fun === "lower"){
+                   return Utils::lower($v);
+                }else if ($fun === "snake"){
+                    return Utils::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(Utils::trimEmpty($contentsStr))){
+                                            $childContents.= $contentsStr;
+                                        }
+                                    }
+                                }
+                                $itemStrContent.= str_replace($childLabel['str'], $childContents,$forOption['content']);
+                            }
+                            $rowContent=$this->replaceParams($itemStrContent,$rowParams);
+                            $itemContentStr=$this->replaceIf($rowContent,$rowParams);
+                            if (!empty(Utils::trimEmpty($itemContentStr))){
+                                $itemContent.= $itemContentStr;
+                            }
+                        }else{
+                            $rowContent=$this->getForContent($forOption['content'],$rowParams);
+                            if (empty(Utils::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;
+        }
+    }
+
+
+}

+ 48 - 0
vendor/hg/apidoc/src/parseApi/CacheApiData.php

@@ -0,0 +1,48 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parseApi;
+
+
+use think\facade\Cache;
+use think\facade\Config;
+
+class CacheApiData
+{
+    protected $config = [];
+
+    public function __construct()
+    {
+        $this->config = Config::get('apidoc');
+    }
+
+
+    /**
+     * 获取接口缓存数据
+     * @param string $appKey
+     * @param string $cacheFileName
+     * @return array|false
+     */
+    public function get(string $appKey)
+    {
+        $json = Cache::get("apidoc_".$appKey);
+        return $json;
+
+    }
+
+    /**
+     * 设置接口缓存
+     * @param string $appKey
+     * @param array $json
+     * @return array|false
+     */
+    public function set(string $appKey, array $json)
+    {
+        if (empty($json)) {
+            return false;
+        }
+        Cache::tag("apidoc")->set("apidoc_".$appKey,$json);
+
+
+    }
+}

+ 863 - 0
vendor/hg/apidoc/src/parseApi/ParseAnnotation.php

@@ -0,0 +1,863 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parseApi;
+
+use Doctrine\Common\Annotations\AnnotationReader;
+use hg\apidoc\Utils;
+use ReflectionClass;
+use Symfony\Component\ClassLoader\ClassMapGenerator;
+use think\annotation\route\Resource;
+use think\annotation\Route;
+use hg\apidoc\annotation\Group;
+use hg\apidoc\annotation\Sort;
+use hg\apidoc\annotation\Param;
+use hg\apidoc\annotation\Title;
+use hg\apidoc\annotation\Desc;
+use hg\apidoc\annotation\Md;
+use hg\apidoc\annotation\ParamMd;
+use hg\apidoc\annotation\ReturnedMd;
+use hg\apidoc\annotation\Author;
+use hg\apidoc\annotation\Tag;
+use hg\apidoc\annotation\Header;
+use hg\apidoc\annotation\Returned;
+use hg\apidoc\annotation\ParamType;
+use hg\apidoc\annotation\Url;
+use hg\apidoc\annotation\Method;
+use hg\apidoc\annotation\Before;
+use hg\apidoc\annotation\After;
+use hg\apidoc\annotation\ContentType;
+use think\annotation\route\Group as RouteGroup;
+use think\facade\App;
+use think\facade\Config;
+
+class ParseAnnotation
+{
+
+    protected $config = [];
+
+    protected $reader;
+
+    //tags,当前应用/版本所有的tag
+    protected $tags = array();
+
+    //groups,当前应用/版本的分组name
+    protected $groups = array();
+
+    protected $controller_layer = "";
+
+    protected $currentApp = [];
+
+    public function __construct()
+    {
+        $this->reader = new AnnotationReader();
+        $this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
+        if (!empty($this->config['ignored_annitation'])){
+            foreach ($this->config['ignored_annitation'] as $item) {
+                AnnotationReader::addGlobalIgnoredName($item);
+            }
+        }
+        $this->controller_layer = Config::get('route.controller_layer',"controller");
+    }
+
+    /**
+     * 生成api接口数据
+     * @param string $appKey
+     * @return array
+     */
+    public function renderApiData(string $appKey): array
+    {
+        $currentApps = (new Utils())->getCurrentApps($appKey);
+        $currentApp  = $currentApps[count($currentApps) - 1];
+        $this->currentApp = $currentApp;
+
+        if (!empty($currentApp['controllers']) && count($currentApp['controllers']) > 0) {
+            // 配置的控制器列表
+            $controllers = $this->getConfigControllers($currentApp['path'],$currentApp['controllers']);
+        } else {
+            // 默认读取所有的
+            $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 = Utils::arraySortByKey($apiData);
+        $json = array(
+            "data"   => $apiList,
+            "tags"   => $this->tags,
+            "groups" => $this->groups,
+        );
+        return $json;
+    }
+
+    /**
+     * 获取生成文档的控制器列表
+     * @param string $path
+     * @return array
+     */
+    protected function getConfigControllers(string $path,$appControllers): array
+    {
+        $controllers = [];
+        $configControllers = $appControllers;
+        if (!empty($configControllers) && count($configControllers) > 0) {
+            foreach ($configControllers as $item) {
+                if ( strpos($item, $path) !== false && class_exists($item)) {
+                    $controllers[] = $item;
+                }
+            }
+        }
+        return $controllers;
+    }
+
+    /**
+     * 获取目录下的控制器列表
+     * @param string $path
+     * @return array
+     */
+    protected function getDirControllers(string $path): array
+    {
+        if ($path) {
+            if (strpos(App::getRootPath(), '/') !== false) {
+                $pathStr = str_replace("\\", "/", $path);
+            } else {
+                $pathStr = $path;
+            }
+            $dir = App::getRootPath() . $pathStr;
+        } else {
+            $dir = App::getRootPath() . $this->controller_layer;
+        }
+        $controllers = [];
+        if (is_dir($dir)) {
+            $controllers = $this->scanDir($dir, $path);
+        }
+        return $controllers;
+    }
+
+    /**
+     * 处理指定目录下的控制器
+     * @param string $dir
+     * @param string $appPath
+     * @return array
+     */
+    protected function scanDir(string $dir, string $appPath): array
+    {
+        $list = [];
+        foreach (ClassMapGenerator::createMap($dir) as $class => $path) {
+            if (strpos($class, $appPath) !== false || strpos($class, "\\") !== false) {
+                $classNamespace = $class;
+            }else{
+                $pathStr = str_replace("/", "\\", $path);
+                $pathArr   = explode($appPath, $pathStr);
+                if (!empty($pathArr[1])){
+                    $classNamespace = $appPath.str_replace(".php", "", $pathArr[1]);
+                }else{
+                    continue;
+                }
+            }
+            if (
+                !isset($this->config['filter_controllers']) ||
+                (isset($this->config['filter_controllers']) && !in_array($classNamespace, $this->config['filter_controllers'])) &&
+                $this->config['definitions'] != $classNamespace
+            ) {
+                if (strpos($classNamespace, '\\') === false) {
+                    $list[] = $appPath . "\\" . $classNamespace;
+                } else {
+                    $list[] = $classNamespace;
+                }
+            }
+        }
+        return $list;
+    }
+
+    protected function parseController($class)
+    {
+
+        $data                 = [];
+        $refClass             = new ReflectionClass($class);
+        $classTextAnnotations = $this->parseTextAnnotation($refClass);
+        if (in_array("NotParse", $classTextAnnotations)) {
+            return false;
+        }
+        $title = $this->reader->getClassAnnotation($refClass, Title::class);
+        $group = $this->reader->getClassAnnotation($refClass, Group::class);
+        $sort = $this->reader->getClassAnnotation($refClass, Sort::class);
+
+        $routeGroup         = $this->reader->getClassAnnotation($refClass, RouteGroup::class);
+        $controllersNameArr = explode("\\", $class);
+        $controllersName    = $controllersNameArr[count($controllersNameArr) - 1];
+        $data['controller'] = $controllersName;
+        $data['group']      = !empty($group->value) ? $group->value : null;
+        $data['sort']      = !empty($sort->value) ? $sort->value : null;
+        if (!empty($data['group']) && !in_array($data['group'], $this->groups)) {
+            $this->groups[] = $data['group'];
+        }
+        $data['title'] = !empty($title) && !empty($title->value) ? $title->value : "";
+
+        if (empty($title)) {
+            if (!empty($classTextAnnotations) && count($classTextAnnotations) > 0) {
+                $data['title'] = $classTextAnnotations[0];
+            } else {
+                $data['title'] = $controllersName;
+            }
+        }
+        $data['title'] = Utils::getLang($data['title']);
+        $methodList       = [];
+        $data['menu_key'] = Utils::createRandKey($data['controller']);
+
+        foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
+
+            $methodItem = $this->parseApiMethod($refClass,$refMethod,$routeGroup);
+            if ($methodItem===false){
+                continue;
+            }
+            if (in_array("NotDebug", $classTextAnnotations)) {
+                $methodItem['notDebug'] = true;
+            }
+            $methodList[] = $methodItem;
+
+        }
+        $data['children'] = $methodList;
+        if (count($methodList)===0){
+            return false;
+        }
+        return $data;
+    }
+
+
+    protected function parseApiMethod($refClass,$refMethod,$routeGroup){
+        $config               = $this->config;
+        if (empty($refMethod->name)) {
+            return false;
+        }
+        $methodItem = $this->parseAnnotation($refMethod, true,"controller");
+        if (!count((array)$methodItem)) {
+            return false;
+        }
+        $textAnnotations = $this->parseTextAnnotation($refMethod);
+        // 标注不解析的方法
+        if (in_array("NotParse", $textAnnotations)) {
+            return false;
+        }
+        if (
+            in_array("NotDebug", $textAnnotations) ||
+            (isset($config['notDebug']) && $config['notDebug']===true) ||
+            (isset($this->currentApp['notDebug']) && $this->currentApp['notDebug']===true)
+        ) {
+            $methodItem['notDebug'] = true;
+        }
+        // 无标题,且有文本注释
+        if (empty($methodItem['title']) && !empty($textAnnotations) && count($textAnnotations) > 0) {
+            $methodItem['title'] = Utils::getLang($textAnnotations[0]);
+        }
+        // 添加统一headers请求头参数
+        if ((!empty($config['headers']) || !empty($this->currentApp['headers'])) && !in_array("NotHeaders", $textAnnotations)) {
+            $headers = [];
+            $configHeaders = !empty($config['headers'])?$config['headers']:[];
+            if (!empty($this->currentApp['headers'])){
+                $configHeaders = Utils::arrayMergeAndUnique("name", $configHeaders, $this->currentApp['headers']);
+            }
+            foreach ($configHeaders as $headerItem){
+                $headerItem['desc'] = Utils::getLang($headerItem['desc']);
+                $headers[] = $headerItem;
+            }
+            if (!empty($methodItem['header'])) {
+                $methodItem['header'] = Utils::arrayMergeAndUnique("name", $headers, $methodItem['header']);
+            } else {
+                $methodItem['header'] = $headers;
+            }
+        }
+
+        // 添加统一params请求参数
+        if ((!empty($config['parameters']) || !empty($this->currentApp['parameters'])) && !in_array("NotParameters", $textAnnotations)) {
+            $params = [];
+            $configParams = !empty($config['parameters'])?$config['parameters']:[];
+            if (!empty($this->currentApp['parameters'])){
+                $configParams = Utils::arrayMergeAndUnique("name", $configParams, $this->currentApp['parameters']);
+            }
+            foreach ($configParams as $paramItem){
+                $paramItem['desc'] = Utils::getLang($paramItem['desc']);
+                $params[] = $paramItem;
+            }
+
+            if (!empty($methodItem['param'])) {
+                $methodItem['param'] = Utils::arrayMergeAndUnique("name", $configParams, $methodItem['param']);
+            } else {
+                $methodItem['param'] = $params;
+            }
+        }
+        // 添加responses统一响应体
+        if (
+            !empty($config['responses']) &&
+            !is_string($config['responses']) &&
+            !in_array("NotResponses", $textAnnotations)
+        ) {
+            // 显示在响应体中
+            $returned = [];
+            $hasMian  = false;
+            $responsesData = $config['responses'];
+            // 合并统一响应体及响应参数相同的字段
+            $returnData = [];
+            $resKeys = [];
+            foreach ($responsesData as $resItem) {
+                $resKeys[]=$resItem['name'];
+            }
+            foreach ($methodItem['return'] as $returnItem){
+                if (!(in_array($returnItem['name'],$resKeys) && !empty($returnItem['source']) &&  $returnItem['source']==='controller' && !empty($returnItem['replaceGlobal']))){
+                    $returnData[]=$returnItem;
+                }
+            }
+
+            foreach ($responsesData as $resItem) {
+                $resData = $resItem;
+                $globalFind = Utils::getArrayFind($methodItem['return'],function ($item)use ($resItem){
+                    if ($item['name'] == $resItem['name'] && !empty($item['source']) && $item['source']==='controller' && !empty($item['replaceGlobal'])){
+                        return true;
+                    }
+                    return false;
+                });
+                if (!empty($globalFind)){
+                    $resData = array_merge($resItem,$globalFind);
+                }else if (!empty($resData['main']) && $resData['main'] === true) {
+                    $resData['children'] = $returnData;
+
+                    $resData['resKeys']=$resKeys;
+                    $hasMian           = true;
+                }
+                $resData['find'] =$globalFind;
+                $resData['desc'] = Utils::getLang($resData['desc']);
+                $returned[] = $resData;
+            }
+            if (!$hasMian) {
+                $returned = Utils::arrayMergeAndUnique("name", $returned, $methodItem['return']);
+            }
+            $methodItem['return'] = $returned;
+        }
+        // 默认method
+        if (empty($methodItem['method'])) {
+            $methodItem['method'] = !empty($config['default_method']) ? $config['default_method'] : 'GET';
+        }
+        $methodItem['method'] = strtoupper($methodItem['method']);
+
+
+        // 默认default_author
+        if (empty($methodItem['author']) && !empty($config['default_author']) && !in_array("NotDefaultAuthor", $textAnnotations)) {
+            $methodItem['author'] = $config['default_author'];
+        }
+
+        // Tags
+        if (!empty($methodItem['tag'])) {
+            $tagText = $methodItem['tag'];
+            if (strpos($tagText, ',') !== false) {
+                $tagArr = explode(",", $tagText);
+                $tagList = [];
+                foreach ($tagArr as $tag) {
+                    $t = Utils::getLang($tag);
+                    $tagList[]=$t;
+                    if (!in_array($tag, $this->tags)) {
+                        $this->tags[] =  $t;
+                    }
+                }
+                $methodItem['tag'] = $tagList;
+            } else {
+                $methodItem['tag'] = [Utils::getLang($tagText)];
+                if (!in_array($tagText, $this->tags)) {
+                    $this->tags[] = $tagText;
+                }
+            }
+        }
+        // 无url,自动生成
+        if (empty($methodItem['url'])) {
+            $methodItem['url'] = $this->autoCreateUrl($refClass->name,$refMethod);
+        } else if (!empty($methodItem['url']) && $methodItem['isAnnotationUrl']===false && !empty($routeGroup->value)) {
+            // 路由分组,url加上分组
+            $methodItem['url'] = '/' . $routeGroup->value . '/' . $methodItem['url'];
+        }else if (!empty($methodItem['url']) && substr($methodItem['url'], 0, 1) != "/") {
+            $methodItem['url'] = "/" . $methodItem['url'];
+        }
+        $methodItem['name']     = $refMethod->name;
+        $methodItem['menu_key'] =$this->currentApp['folder']."_".$methodItem['method']."_".$methodItem['url'];
+        return $methodItem;
+    }
+
+    /**
+     * 自动生成url
+     * @param $method
+     * @return string
+     */
+    protected function autoCreateUrl($classPath,$method): string
+    {
+        if (!empty($this->config['auto_url']) && !empty($this->config['auto_url']['custom']) && is_callable($this->config['auto_url']['custom'])){
+           return $this->config['auto_url']['custom']($classPath,$method->name);
+        }
+        $searchString = $this->controller_layer . '\\';
+        $substr = substr(strstr($classPath, $searchString), strlen($searchString));
+        $multistage_route_separator = ".";
+        if (!empty($this->config['auto_url']) && !empty($this->config['auto_url']['multistage_route_separator'])){
+            $multistage_route_separator = $this->config['auto_url']['multistage_route_separator'];
+        }
+        $pathArr = explode("\\", str_replace($substr, str_replace('\\', $multistage_route_separator, $substr), $classPath));
+        $filterPathNames = array($this->controller_layer);
+        $appNameespace = App::getNamespace();
+        if (strpos($appNameespace, '\\') !== false){
+            $appNameespaceArr    = explode("\\", $appNameespace);
+            $filterPathNames[] = $appNameespaceArr[0];
+        }else{
+            $filterPathNames[]=App::getNamespace();
+        }
+        $classUrlArr = [];
+        foreach ($pathArr as $item) {
+            if (!in_array($item, $filterPathNames)) {
+                if (!empty($this->config['auto_url']) && !empty($this->config['auto_url']['letter_rule'])){
+                    switch ($this->config['auto_url']['letter_rule']) {
+                        case 'lcfirst':
+                            $classUrlArr[] = lcfirst($item);
+                            break;
+                        case 'ucfirst':
+                            $classUrlArr[] = ucfirst($item);
+                            break;
+                        default:
+                            $classUrlArr[] = $item;
+                    }
+                }else{
+                    $classUrlArr[] = $item;
+                }
+            }
+        }
+        $classUrl = implode('/', $classUrlArr);
+        return '/' . $classUrl . '/' . $method->name;
+    }
+
+    /**
+     * ref引用
+     * @param $refPath
+     * @param bool $enableRefService
+     * @return false|string[]
+     */
+    protected function renderRef(string $refPath, bool $enableRefService = true): array
+    {
+        $res = ['type' => 'model'];
+        // 通用定义引入
+        if (strpos($refPath, '\\') === false) {
+            $config      = $this->config;
+            $refPath     = $config['definitions'] . '\\' . $refPath;
+            $data        = $this->renderService($refPath);
+            $res['type'] = "service";
+            $res['data'] = $data;
+            return $res;
+        }
+        // 模型引入
+        $modelData = (new ParseModel($this->reader))->renderModel($refPath);
+        if ($modelData !== false) {
+            $res['data'] = $modelData;
+            return $res;
+        }
+        if ($enableRefService === false) {
+            return false;
+        }
+        $data        = $this->renderService($refPath);
+        $res['type'] = "service";
+        $res['data'] = $data;
+        return $res;
+    }
+
+    /**
+     * 解析注释引用
+     * @param $refPath
+     * @return array
+     * @throws \ReflectionException
+     */
+    protected function renderService(string $refPath)
+    {
+        $pathArr    = explode("\\", $refPath);
+        $methodName = $pathArr[count($pathArr) - 1];
+        unset($pathArr[count($pathArr) - 1]);
+        $classPath    = implode("\\", $pathArr);
+        $classReflect = new \ReflectionClass($classPath);
+        $methodName   = trim($methodName);
+        $refMethod    = $classReflect->getMethod($methodName);
+        $res          = $this->parseAnnotation($refMethod, true);
+        return $res;
+    }
+
+    /**
+     * 处理Param/Returned的字段名name、params子级参数
+     * @param $values
+     * @return array
+     */
+    protected function handleParamValue($values, string $field = 'param'): array
+    {
+        $name   = "";
+        $params = [];
+        if (!empty($values) && is_array($values) && count($values) > 0) {
+            foreach ($values as $item) {
+                if (is_string($item)) {
+                    $name = $item;
+                } else if (is_object($item)) {
+                    if (!empty($item->ref)) {
+                        $refRes = $this->renderRef($item->ref, true);
+                        $params = $this->handleRefData($params, $refRes, $item, $field);
+                    } else {
+                        $param         = [
+                            "name"    => "",
+                            "type"    => $item->type,
+                            "desc"    => Utils::getLang($item->desc),
+                            "default" => $item->default,
+                            "require" => $item->require,
+                            "childrenType"=> $item->childrenType
+                        ];
+                        if (!empty($item->mock)){
+                            $param['mock']=$item->mock;
+                        }
+                        $children      = $this->handleParamValue($item->value);
+                        $param['name'] = $children['name'];
+                        if (count($children['params']) > 0) {
+                            $param['children'] = $children['params'];
+                        }
+                        $params[] = $param;
+                    }
+                }
+            }
+        } else {
+            $name = $values;
+        }
+        return ['name' => $name, 'params' => $params];
+    }
+
+    /**
+     * 解析方法注释
+     * @param $refMethod
+     * @param bool $enableRefService 是否终止service的引入
+     * @param string $source 注解来源
+     * @return array
+     */
+    protected function parseAnnotation($refMethod, bool $enableRefService = true,$source=""): array
+    {
+        $data = [];
+        if ($annotations = $this->reader->getMethodAnnotations($refMethod)) {
+            $headers = [];
+            $params  = [];
+            $returns = [];
+            $before = [];
+            $after = [];
+            $isAnnotationUrl = false;
+
+            foreach ($annotations as $annotation) {
+                switch (true) {
+                    case $annotation instanceof Param:
+                        $params = $this->handleParamAndReturned($params,$annotation,'param',$enableRefService);
+                        break;
+                    case $annotation instanceof Returned:
+
+                        $returns = $this->handleParamAndReturned($returns,$annotation,'return',$enableRefService,$source);
+                        break;
+                    case $annotation instanceof Header:
+                        if (!empty($annotation->ref)) {
+                            $refRes  = $this->renderRef($annotation->ref, $enableRefService);
+                            $headers = $this->handleRefData($headers, $refRes, $annotation, 'header');
+                        } else {
+                            $param     = [
+                                "name"    => $annotation->value,
+                                "desc"    => Utils::getLang($annotation->desc),
+                                "require" => $annotation->require,
+                                "type"    => $annotation->type,
+                                "default" => $annotation->default,
+                            ];
+                            $headers[] = $param;
+                        }
+                        break;
+                    case $annotation instanceof Route:
+                        if (empty($data['method'])) {
+                            $data['method'] = $annotation->method;
+                        }
+                        if (empty($data['url'])) {
+                            $data['url'] = $annotation->value;
+                        }
+                        break;
+                    case $annotation instanceof Author:
+                        $data['author'] = $annotation->value;
+                        break;
+
+                    case $annotation instanceof Title:
+                        $data['title'] = Utils::getLang($annotation->value);
+                        break;
+                    case $annotation instanceof Desc:
+                        $data['desc'] = Utils::getLang($annotation->value);
+                        if (!empty($annotation->mdRef)){
+                            $data['md'] = $annotation->mdRef;
+                        }
+                        break;
+                    case $annotation instanceof Md:
+                        $data['md'] = $annotation->value;
+                        if (!empty($annotation->ref)){
+                            $data['md'] = (new ParseMarkdown())->getContent("",$annotation->ref);
+                        }
+                        break;
+                    case $annotation instanceof ParamMd:
+                        $data['paramMd'] = $annotation->value;
+                        if (!empty($annotation->ref)){
+                            $data['paramMd'] = (new ParseMarkdown())->getContent("",$annotation->ref);
+                        }
+                        break;
+                    case $annotation instanceof ReturnedMd:
+                        $data['returnMd'] = $annotation->value;
+                        if (!empty($annotation->ref)){
+                            $data['returnMd'] = (new ParseMarkdown())->getContent("",$annotation->ref);
+                        }
+                        break;
+                    case $annotation instanceof ParamType:
+                        $data['paramType'] = $annotation->value;
+                        break;
+                    case $annotation instanceof Url:
+                        $data['url'] = $annotation->value;
+                        $isAnnotationUrl=true;
+                        break;
+                    case $annotation instanceof Method:
+                        $data['method'] = $annotation->value;
+                        break;
+                    case $annotation instanceof Tag:
+                        $data['tag'] = $annotation->value;
+                        break;
+                    case $annotation instanceof ContentType:
+                    $data['contentType'] = $annotation->value;
+                    break;
+                    case $annotation instanceof Before:
+                        $beforeAnnotation = $this->handleEventAnnotation($annotation,'before');
+                        $before =  array_merge($before,$beforeAnnotation);
+                        break;
+                    case $annotation instanceof After:
+                        $afterAnnotation = $this->handleEventAnnotation($annotation,'after');
+                        $after =array_merge($after,$afterAnnotation);
+                        break;
+                }
+            }
+            if ($headers && count($headers) > 0) {
+                $data['header'] = $headers;
+            }
+            $data['param']  = $params;
+            $data['return'] = $returns;
+            $data['before'] = $before;
+            $data['after'] = $after;
+            $data['isAnnotationUrl'] = $isAnnotationUrl;
+        }
+        return $data;
+    }
+
+    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 = Utils::objectToArray($annotation);
+            $valueList = [];
+            foreach ($annotation->value as $valueItem){
+                $valueItemInfo = Utils::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{
+            return [$annotation];
+        }
+    }
+
+
+    /**
+     * 处理请求参数与返回参数
+     * @param $params
+     * @param $annotation
+     * @param string $type
+     * @param false $enableRefService
+     * @param string $source 注解来源
+     * @return array
+     */
+    protected function handleParamAndReturned($params,$annotation,$type="param",$enableRefService=false,$source=""){
+        if (!empty($annotation->ref)) {
+            $refRes = $this->renderRef($annotation->ref, $enableRefService);
+            $params = $this->handleRefData($params, $refRes, $annotation, $type,$source);
+        } else {
+
+            $param =  Utils::objectToArray($annotation);
+            $param["source"] = $source;
+            $param["desc"] = Utils::getLang($param['desc']);
+
+            $children      = $this->handleParamValue($annotation->value, $type);
+            $param['name'] = $children['name'];
+            if (count($children['params']) > 0) {
+                $param['children'] = $children['params'];
+
+            }
+            if ($annotation->type === 'tree' ) {
+                // 类型为tree的
+                $param['children'][] = [
+                    'children' => $children['params'],
+                    'name'   => !empty($annotation->childrenField)?$annotation->childrenField:"children",
+                    'type'   => 'array',
+                    'desc'   => Utils::getLang($annotation->childrenDesc),
+                ];
+            }
+            // 合并同级已有的字段
+            $params = Utils::arrayMergeAndUnique("name", $params, [$param]);
+        }
+            return $params;
+    }
+
+    /**
+     * 解析非注解文本注释
+     * @param $refMethod
+     * @return array|false
+     */
+    protected 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、returned 参数
+     * @param $params
+     * @param $refRes
+     * @param $annotation
+     * @param string|null $source 注解来源
+     * @return array
+     */
+    protected function handleRefData($params, $refRes, $annotation, string $field,$source=""): array
+    {
+        if ($refRes['type'] === "model" && count($refRes['data']) > 0) {
+            // 模型引入
+            $data = $refRes['data'];
+        } else if ($refRes['type'] === "service" && !empty($refRes['data']) && !empty($refRes['data'][$field])) {
+            // service引入
+            $data = $refRes['data'][$field];
+        } else {
+            return $params;
+        }
+        // 过滤field
+        if (!empty($annotation->field)) {
+            $data = (new Utils())->filterParamsField($data, $annotation->field, 'field');
+        }
+        // 过滤withoutField
+        if (!empty($annotation->withoutField)) {
+            $data = (new Utils())->filterParamsField($data, $annotation->withoutField, 'withoutField');
+        }
+
+        if (!empty($annotation->value)) {
+            $item =  Utils::objectToArray($annotation);
+            $item['children'] = $data;
+            $item['source'] = $source;
+            $param["desc"] = Utils::getLang($item['desc']);
+
+            $children      = $this->handleParamValue($annotation->value, 'param');
+            $item['name'] = $children['name'];
+            if (count($children['params']) > 0) {
+                $item['children'] = Utils::arrayMergeAndUnique("name",$data,$children['params']);
+            }
+            if ($annotation->type === 'tree' ) {
+                // 类型为tree的
+                $item['children'][] = [
+                    'children' => $item['children'],
+                    'name'   =>!empty($annotation->childrenField) ?$annotation->childrenField:'children',
+                    'type'   => 'array',
+                    'desc'   => Utils::getLang($annotation->childrenDesc),
+                ];
+            }
+            $params[] = $item;
+
+
+        } else {
+            $params = Utils::arrayMergeAndUnique("name",$params,$data);
+//            $params = array_merge($params, $data);
+        }
+        return $params;
+    }
+
+
+    /**
+     * 对象分组到tree
+     * @param $tree
+     * @param $objectData
+     * @param string $childrenField
+     * @return array
+     */
+    public function objtctGroupByTree($tree,$objectData,$childrenField='children'){
+        $data = [];
+        foreach ($tree as $node){
+            if (!empty($node[$childrenField])){
+                $node[$childrenField] = $this->objtctGroupByTree($node[$childrenField],$objectData);
+            }else if (!empty($objectData[$node['name']])){
+                $node[$childrenField] =  $objectData[$node['name']];
+            }
+            $node['menu_key'] = Utils::createRandKey( $node['name']);
+            $data[] = $node;
+        }
+        return $data;
+    }
+
+    /**
+     * 合并接口到应用分组
+     * @param $apiData
+     * @param $groups
+     * @return array
+     */
+    public function mergeApiGroup($apiData,$groups){
+        if (empty($groups) || count($apiData)<1){
+            return $apiData;
+        }
+        $apiObject = [];
+        foreach ($apiData as $controller){
+            if (!empty($controller['group'])){
+                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 = $this->objtctGroupByTree($groups,$apiObject);
+        return $res;
+    }
+}

+ 117 - 0
vendor/hg/apidoc/src/parseApi/ParseMarkdown.php

@@ -0,0 +1,117 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parseApi;
+
+use think\facade\App;
+use hg\apidoc\Utils;
+use think\facade\Config;
+use think\facade\Lang;
+
+class ParseMarkdown
+{
+    protected $config = [];
+
+    public function __construct()
+    {
+        $this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
+    }
+
+    /**
+     * 获取md文档菜单
+     * @return array
+     */
+    public function getDocsMenu(string $lang): array
+    {
+        $config  = $this->config;
+        $docData = [];
+        if (!empty($config['docs']) && count($config['docs']) > 0) {
+            $docData = $this->handleDocsMenuData($config['docs'],$lang);
+        }
+        return $docData;
+    }
+
+    /**
+     * 处理md文档菜单数据
+     * @param array $menus
+     * @return array
+     */
+    protected function handleDocsMenuData(array $menus,string $lang): array
+    {
+        $list = [];
+        foreach ($menus as $item) {
+            $item['title']     = Utils::getLang($item['title']);
+            if(!empty($item['path'])){
+//                $lang = Lang::getLangSet();
+                $item['path'] = Utils::replaceTemplate($item['path'],['lang'=>$lang]);
+            }
+            if (!empty($item['children']) && count($item['children']) > 0) {
+                $item['children']    = $this->handleDocsMenuData($item['children'],$lang);
+                $item['menu_key'] = Utils::createRandKey("md_group");
+            } else {
+                $item['type']     = 'md';
+                $item['menu_key'] = Utils::createRandKey("md");
+            }
+            $list[]           = $item;
+        }
+        return $list;
+    }
+
+
+    /**
+     * 获取md文档内容
+     * @param string $appKey
+     * @param string $path
+     * @return string
+     */
+    public function getContent(string $appKey, string $path,$lang="")
+    {
+        if (!empty($appKey)){
+            $currentApps = (new Utils())->getCurrentApps($appKey);
+            $fullPath      = (new Utils())->replaceCurrentAppTemplate($path, $currentApps);
+        }else{
+            $fullPath = $path;
+        }
+        $fullPath = Utils::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    = App::getRootPath() . $mdPath . $fileSuffix;
+        $contents    = Utils::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;
+    }
+
+
+}

+ 245 - 0
vendor/hg/apidoc/src/parseApi/ParseModel.php

@@ -0,0 +1,245 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parseApi;
+
+use Doctrine\Common\Annotations\Reader;
+use hg\apidoc\exception\ErrorException;
+use think\Db as Db5;
+use think\facade\Db;
+use hg\apidoc\annotation\Field;
+use hg\apidoc\annotation\WithoutField;
+use hg\apidoc\annotation\AddField;
+use think\helper\Str;
+use hg\apidoc\Utils;
+
+class ParseModel
+{
+    protected $reader;
+
+    public function __construct(Reader $reader)
+    {
+        $this->reader = $reader;
+    }
+
+    /**
+     * 生成模型数据
+     * @param string $path
+     * @return array|false
+     * @throws \ReflectionException
+     */
+    public function renderModel(string $path)
+    {
+
+        if (strpos($path, '@') !== false){
+            $pathArr   = explode("@", $path);
+            $modelClassPath = $pathArr[0];
+            $methodName =  $pathArr[1];
+            $model = $this->getModelClass($modelClassPath);
+            return $this->parseModelTable($model,$modelClassPath,$methodName);
+        }else if (class_exists($path)) {
+            $model = $this->getModelClass($path);
+            return $this->parseModelTable($model,$path,"");
+        } else {
+            $modelClassArr   = explode("\\", $path);
+            $methodName = $modelClassArr[count($modelClassArr) - 1];
+            unset($modelClassArr[count($modelClassArr) - 1]);
+            $modelClassPath  = implode("\\", $modelClassArr);
+            if (class_exists($modelClassPath)){
+                $model = $this->getModelClass($modelClassPath);
+                return $this->parseModelTable($model,$modelClassPath,$methodName);
+            }else{
+                throw new ErrorException("ref file not exists", 412, [
+                    'filepath' => $path
+                ]);
+            }
+        }
+    }
+
+    protected function parseModelTable($model,$path,$methodName=""){
+        if (!is_callable(array($model, 'getTable'))) {
+            return false;
+        }
+        $classReflect    = new \ReflectionClass($path);
+        // 获取所有模型属性
+        $propertys = $classReflect->getDefaultProperties();
+
+        $table = $this->getTableDocument($model, $propertys);
+        if (empty($methodName)){
+            return $table;
+        }
+        $methodAction    = $classReflect->getMethod($methodName);
+        // 模型注释-field
+        if ($fieldAnnotations = $this->reader->getMethodAnnotation($methodAction, Field::class)) {
+            $table = (new Utils())->filterParamsField($table, $fieldAnnotations->value, 'field');
+        }
+        // 模型注释-withoutField
+        if ($fieldAnnotations = $this->reader->getMethodAnnotation($methodAction, WithoutField::class)) {
+            $table = (new Utils())->filterParamsField($table, $fieldAnnotations->value, 'withoutField');
+        }
+        // 模型注释-addField
+        if ($annotations = $this->reader->getMethodAnnotations($methodAction)) {
+            foreach ($annotations as $annotation) {
+                switch (true) {
+                    case $annotation instanceof AddField:
+                        $param         = [
+                            "name"    => "",
+                            "desc"    => $annotation->desc,
+                            "require" => $annotation->require,
+                            "type"    => $annotation->type,
+                            "default" => $annotation->default
+                        ];
+                        $children      = $this->handleParamValue($annotation->value);
+                        $param['name'] = $children['name'];
+                        if (count($children['params']) > 0) {
+                            $param['children'] = $children['params'];
+                        }
+                        $isExists = false;
+                        $newTable = [];
+                        foreach ($table as $item) {
+                            if ($param['name'] === $item['name']) {
+                                $isExists   = true;
+                                $newTable[] = $param;
+                            } else {
+                                $newTable[] = $item;
+                            }
+                        }
+                        $table = $newTable;
+                        if (!$isExists) {
+                            $table[] = $param;
+                        }
+                        break;
+                }
+            }
+        }
+        return $table;
+    }
+
+    /**
+     * 处理字段参数
+     * @param $values
+     * @return array
+     */
+    protected function handleParamValue($values): array
+    {
+        $name   = "";
+        $params = [];
+        if (!empty($values) && is_array($values) && count($values) > 0) {
+            foreach ($values as $item) {
+                if (is_string($item)) {
+                    $name = $item;
+                } else if (is_object($item)) {
+                    $param         = [
+                        "name"    => "",
+                        "type"    => $item->type,
+                        "desc"    => $item->desc,
+                        "default" => $item->default,
+                        "require" => $item->require,
+                    ];
+                    $children      = $this->handleParamValue($item->value);
+                    $param['name'] = $children['name'];
+                    if (count($children['params']) > 0) {
+                        $param['children'] = $children['params'];
+                    }
+                    $params[] = $param;
+                }
+            }
+        } else {
+            $name = $values;
+        }
+        return ['name' => $name, 'params' => $params];
+    }
+
+    /**
+     * 获取模型实例
+     * @param $method
+     * @return mixed|null
+     */
+    public function getModelClass($namespaceName)
+    {
+        if (!empty($namespaceName) && class_exists($namespaceName)) {
+            $modelInstance = new $namespaceName();
+            return $modelInstance;
+        } else {
+            return null;
+        }
+    }
+
+
+    /**
+     * 获取模型注解数据
+     * @param $model
+     * @param $propertys
+     * @return array
+     */
+    public function getTableDocument($model,array $propertys):array
+    {
+
+        $tp_version = \think\facade\App::version();
+        if (substr($tp_version, 0, 2) == '5.'){
+            $createSQL = Db5::query("show create table " . $model->getTable())[0];
+        }else{
+            $createSQL = Db::query("show create table " . $model->getTable())[0];
+        }
+
+        $createTable = "";
+        if (!empty($createSQL['Create Table'])){
+            $createTable = $createSQL['Create Table'];
+        }else  if(!empty($createSQL['create table'])){
+            $createTable = $createSQL['create table'];
+        }else{
+            throw new ErrorException("datatable not exists", 412, $createSQL);
+        }
+        preg_match_all("#[^KEY]`(.*?)` (.*?) (.*?),\n#", $createTable, $matches);
+        $fields       = $matches[1];
+        $types        = $matches[2];
+        $contents     = $matches[3];
+        $fieldComment = [];
+        //组织注释
+        for ($i = 0; $i < count($matches[0]); $i++) {
+            $key     = $fields[$i];
+            $type    = $types[$i];
+            $default = "";
+            $require = "";
+            $desc    = "";
+            $content = $contents[$i];
+            if (strpos($type, '(`') !== false) {
+                continue;
+            }
+            if (strpos($content, 'COMMENT') !== false) {
+                // 存在字段注释
+                preg_match_all("#COMMENT\s*'(.*?)'#", $content, $edscs);
+                if (!empty($edscs[1]) && !empty($edscs[1][0])){
+                    $desc = Utils::getLang($edscs[1][0]);
+
+                }
+
+            }
+            if (strpos($content, 'DEFAULT') !== false) {
+                // 存在字段默认值
+                preg_match_all("#DEFAULT\s*'(.*?)'#", $content, $defaults);
+                $default = $defaults[1] && is_array($defaults[1])?$defaults[1][0]:"";
+            }
+
+            if (strpos($content, 'NOT NULL') !== false) {
+                // 必填字段
+                $require = "1";
+            }
+
+            $name = $key;
+            // 转换字段名为驼峰命名(用于输出)
+            if (isset($propertys['convertNameToCamel']) && $propertys['convertNameToCamel'] === true) {
+                $name = Str::camel($key);
+            }
+            $fieldComment[] = [
+                "name"    => $name,
+                "type"    => $type,
+                "desc"    => $desc,
+                "default" => $default,
+                "require" => $require,
+            ];
+        }
+        return $fieldComment;
+    }
+
+}