From 9ede152a6b036a7bb03c813a4b71276967dbcf38 Mon Sep 17 00:00:00 2001 From: zzx Date: Thu, 9 May 2024 23:03:39 +0800 Subject: [PATCH 1/6] add yii2 support, init controller && provider --- src/controllers/Yii2Controller.php | 172 +++++++++++++++++++++++++++++ src/providers/Yii2Service.php | 68 ++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 src/controllers/Yii2Controller.php create mode 100644 src/providers/Yii2Service.php diff --git a/src/controllers/Yii2Controller.php b/src/controllers/Yii2Controller.php new file mode 100644 index 0000000..2e05de2 --- /dev/null +++ b/src/controllers/Yii2Controller.php @@ -0,0 +1,172 @@ +run(); + + $this->config = ConfigProvider::get(); + if (isset($this->config['enable']) && $this->config['enable'] === false) { + throw new ErrorException("apidoc close"); + } + if (!empty($this->config['request_params'])) { + $this->requestParams = $this->config['request_params']; + } else { + $this->requestParams = (new Request())->param(); + } + if (!empty($this->requestParams['lang']) && !empty($this->config['lang_register_function'])) { + $this->lang = $this->requestParams['lang']; + $this->config['lang_register_function']($this->lang); + } + if ($checkAuth) { + (new Auth($this->config))->checkAuth($this->requestParams); + } + } + + /** + * @inheritdoc + */ + public function behaviors() + { + $behaviors = parent::behaviors(); + unset($behaviors['authenticator']); + + //跨域 + $behaviors['corsFilter'] = [ + 'class' => Cors::class, + 'cors' => [ + // restrict access to + 'Origin' => ['*'], + // Allow methods + 'Access-Control-Request-Method' => ['POST', 'GET', 'OPTIONS', 'HEAD'], + // Allow only headers 'X-Wsse' + 'Access-Control-Request-Headers' => ['*'], + // Allow credentials (cookies, authorization headers, etc.) to be exposed to the browser + 'Access-Control-Allow-Credentials' => false, + // Allow OPTIONS caching + 'Access-Control-Max-Age' => 3600, + // Allow the X-Pagination-Current-Page header to be exposed to the browser. + //'Access-Control-Expose-Headers' => ['access-token', 'app-id'], + ], + ]; + + $behaviors['contentNegotiator'] = [ + 'class' => ContentNegotiator::class, + 'formats' => [ + 'application/json' => Response::FORMAT_JSON + ] + ]; + + return $behaviors; + } + + public function actionConfig() + { + $this->init(); + + $params = $this->requestParams; + if (!empty($params['shareKey'])) { + // 接口分享 + $shareData = (new ApiShare())->checkShareAuth($this->config, $params); + if (!empty($shareData['appKeys'])) { + $config = ConfigProvider::getFeConfig($shareData['appKeys']); + } else { + $config = ConfigProvider::getFeConfig(); + } + } else { + (new Auth($this->config))->checkAuth($params); + $config = ConfigProvider::getFeConfig(); + } + return Helper::showJson(0, "", $config); + } + + public function actionApiMenus() + { + $this->init(); + //$config = $this->config; + $params = $this->requestParams; + if (empty($params['appKey'])) { + throw new ErrorException("appkey not found"); + } + $appKey = $params['appKey']; + $shareData = $this->checkAuth(); + $currentAppConfig = Helper::getCurrentAppConfig($appKey); + $currentApp = $currentAppConfig['appConfig']; + $apiData = $this->getApiMenusByAppKey($appKey); + $groups = !empty($currentApp['groups']) ? $currentApp['groups'] : []; + if (!empty($params['shareKey']) && $shareData['type'] == 'api') { + $apiData['data'] = Helper::filterTreeNodesByKeys($apiData['data'], $shareData['apiKeys'], 'menuKey'); + } + $json = [ + 'data' => $apiData['data'], + 'app' => $currentApp, + 'groups' => $groups, + 'tags' => $apiData['tags'], + ]; + return Helper::showJson(0, "", $json); + } + + /** + * 验证权限 + */ + protected function checkAuth() + { + $config = $this->config; + $params = $this->requestParams; + if (!empty($params['shareKey'])) { + //分享 + $shareData = (new ApiShare())->checkShareAuth($config, $params); + $appKey = !empty($params['appKey']) ? $params['appKey'] : ""; + if (!empty($shareData['appKeys']) && !in_array($appKey, $shareData['appKeys'])) { + throw new ErrorException("share not exists"); + } + return $shareData; + } else { + (new Auth($config))->checkAuth($params); + } + } + + protected function getApiMenusByAppKey($appKey) + { + $config = $this->config; + if (!empty($config['cache']) && $config['cache']['enable']) { + $cacheKey = Helper::getCacheKey('apiMenu', $appKey, $this->lang); + $cacheData = (new Cache())->get($cacheKey); + if ($cacheData && empty($params['reload'])) { + $apiData = $cacheData; + } else { + // 生成数据并缓存 + $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey); + (new Cache())->set($cacheKey, $apiData); + } + } else { + // 生成数据 + $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey); + } + return $apiData; + } +} \ No newline at end of file diff --git a/src/providers/Yii2Service.php b/src/providers/Yii2Service.php new file mode 100644 index 0000000..dd88965 --- /dev/null +++ b/src/providers/Yii2Service.php @@ -0,0 +1,68 @@ +initConfig(); + } + + static function getApidocConfig() + { + $config = require \Yii::getAlias('@common') . '/config/apidoc.php'; + + if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){ + $config['auto_url']['filter_keys'] = ['app','controller']; + } + $config['app_frame'] = "yii2"; + return $config; + } + + static function registerRoute($route) + { + // TODO: Implement registerRoute() method. + } + + static function databaseQuery($sql) + { + // TODO: Implement databaseQuery() method. + } + + static function getRootPath() + { + //dd(Yii::getAlias('@app')); + return Yii::getAlias('@app'); + } + + static function getRuntimePath() + { + //dd(Yii::getAlias('@runtime')); + return Yii::getAlias('@runtime'); + } + + static function setLang($locale) + { + // TODO: Implement setLang() method. + } + + static function getLang($lang) + { + // TODO: Implement getLang() method. + } + + static function handleResponseJson($res) + { + return $res; + } + + static function getTablePrefix() + { + // TODO: Implement getTablePrefix() method. + } +} \ No newline at end of file From 72389e807332120b937ad1f50886d36dc4fad8b0 Mon Sep 17 00:00:00 2001 From: zzx Date: Sat, 11 May 2024 02:19:55 +0800 Subject: [PATCH 2/6] controller && provider enh --- src/controllers/Yii2Controller.php | 148 +++-------------------------- src/providers/Yii2Service.php | 34 ++++++- 2 files changed, 42 insertions(+), 140 deletions(-) diff --git a/src/controllers/Yii2Controller.php b/src/controllers/Yii2Controller.php index 2e05de2..31fbf9b 100644 --- a/src/controllers/Yii2Controller.php +++ b/src/controllers/Yii2Controller.php @@ -2,78 +2,19 @@ namespace hg\apidoc\controllers; -use hg\apidoc\Auth; -use hg\apidoc\exception\ErrorException; -use hg\apidoc\parses\ParseApiMenus; -use hg\apidoc\providers\Yii2Service; -use hg\apidoc\utils\ApiShare; -use hg\apidoc\utils\Cache; -use hg\apidoc\utils\ConfigProvider; -use hg\apidoc\utils\Helper; -use hg\apidoc\utils\Request; +use hg\apidoc\Controller; use yii\filters\ContentNegotiator; -use yii\filters\Cors; -use yii\rest\Controller; use yii\web\Response; -class Yii2Controller extends Controller +class Yii2Controller extends \yii\rest\Controller { - protected $config; - - protected $requestParams = []; - - protected $lang = ""; - - public function init($checkAuth = false) - { - $provider = new Yii2Service(); - $provider->run(); - - $this->config = ConfigProvider::get(); - if (isset($this->config['enable']) && $this->config['enable'] === false) { - throw new ErrorException("apidoc close"); - } - if (!empty($this->config['request_params'])) { - $this->requestParams = $this->config['request_params']; - } else { - $this->requestParams = (new Request())->param(); - } - if (!empty($this->requestParams['lang']) && !empty($this->config['lang_register_function'])) { - $this->lang = $this->requestParams['lang']; - $this->config['lang_register_function']($this->lang); - } - if ($checkAuth) { - (new Auth($this->config))->checkAuth($this->requestParams); - } - } + private Controller $_ctrl; /** * @inheritdoc */ public function behaviors() { - $behaviors = parent::behaviors(); - unset($behaviors['authenticator']); - - //跨域 - $behaviors['corsFilter'] = [ - 'class' => Cors::class, - 'cors' => [ - // restrict access to - 'Origin' => ['*'], - // Allow methods - 'Access-Control-Request-Method' => ['POST', 'GET', 'OPTIONS', 'HEAD'], - // Allow only headers 'X-Wsse' - 'Access-Control-Request-Headers' => ['*'], - // Allow credentials (cookies, authorization headers, etc.) to be exposed to the browser - 'Access-Control-Allow-Credentials' => false, - // Allow OPTIONS caching - 'Access-Control-Max-Age' => 3600, - // Allow the X-Pagination-Current-Page header to be exposed to the browser. - //'Access-Control-Expose-Headers' => ['access-token', 'app-id'], - ], - ]; - $behaviors['contentNegotiator'] = [ 'class' => ContentNegotiator::class, 'formats' => [ @@ -84,89 +25,24 @@ public function behaviors() return $behaviors; } - public function actionConfig() + public function init($checkAuth = false) { - $this->init(); - - $params = $this->requestParams; - if (!empty($params['shareKey'])) { - // 接口分享 - $shareData = (new ApiShare())->checkShareAuth($this->config, $params); - if (!empty($shareData['appKeys'])) { - $config = ConfigProvider::getFeConfig($shareData['appKeys']); - } else { - $config = ConfigProvider::getFeConfig(); - } - } else { - (new Auth($this->config))->checkAuth($params); - $config = ConfigProvider::getFeConfig(); - } - return Helper::showJson(0, "", $config); + $this->_ctrl = new Controller(); + $this->_ctrl->init(); } - public function actionApiMenus() + public function actionConfig() { - $this->init(); - //$config = $this->config; - $params = $this->requestParams; - if (empty($params['appKey'])) { - throw new ErrorException("appkey not found"); - } - $appKey = $params['appKey']; - $shareData = $this->checkAuth(); - $currentAppConfig = Helper::getCurrentAppConfig($appKey); - $currentApp = $currentAppConfig['appConfig']; - $apiData = $this->getApiMenusByAppKey($appKey); - $groups = !empty($currentApp['groups']) ? $currentApp['groups'] : []; - if (!empty($params['shareKey']) && $shareData['type'] == 'api') { - $apiData['data'] = Helper::filterTreeNodesByKeys($apiData['data'], $shareData['apiKeys'], 'menuKey'); - } - $json = [ - 'data' => $apiData['data'], - 'app' => $currentApp, - 'groups' => $groups, - 'tags' => $apiData['tags'], - ]; - return Helper::showJson(0, "", $json); + return $this->_ctrl->getConfig(); } - /** - * 验证权限 - */ - protected function checkAuth() + public function actionApiMenus() { - $config = $this->config; - $params = $this->requestParams; - if (!empty($params['shareKey'])) { - //分享 - $shareData = (new ApiShare())->checkShareAuth($config, $params); - $appKey = !empty($params['appKey']) ? $params['appKey'] : ""; - if (!empty($shareData['appKeys']) && !in_array($appKey, $shareData['appKeys'])) { - throw new ErrorException("share not exists"); - } - return $shareData; - } else { - (new Auth($config))->checkAuth($params); - } + return $this->_ctrl->getApiMenus(); } - protected function getApiMenusByAppKey($appKey) + public function actionDocMenus() { - $config = $this->config; - if (!empty($config['cache']) && $config['cache']['enable']) { - $cacheKey = Helper::getCacheKey('apiMenu', $appKey, $this->lang); - $cacheData = (new Cache())->get($cacheKey); - if ($cacheData && empty($params['reload'])) { - $apiData = $cacheData; - } else { - // 生成数据并缓存 - $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey); - (new Cache())->set($cacheKey, $apiData); - } - } else { - // 生成数据 - $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey); - } - return $apiData; + return $this->_ctrl->getMdMenus(); } } \ No newline at end of file diff --git a/src/providers/Yii2Service.php b/src/providers/Yii2Service.php index dd88965..675b9e6 100644 --- a/src/providers/Yii2Service.php +++ b/src/providers/Yii2Service.php @@ -3,16 +3,34 @@ namespace hg\apidoc\providers; use Yii; +use yii\base\Component; +use yii\base\InvalidConfigException; +use yii\helpers\Inflector; -class Yii2Service +class Yii2Service extends Component { use BaseService; - public function run() + /** + * @inheritdoc + */ + public function init() { $this->initConfig(); + self::registerApidocRoutes(); + + Yii::$app->controllerMap = [ + 'apidoc' => [ + 'class' => '\hg\apidoc\controllers\Yii2Controller', + 'enableCsrfValidation' => false, + ], + ]; } + /** + * @inheritdoc + * @return array|mixed + */ static function getApidocConfig() { $config = require \Yii::getAlias('@common') . '/config/apidoc.php'; @@ -24,9 +42,17 @@ static function getApidocConfig() return $config; } + /** + * @inheritdoc + * @throws InvalidConfigException + */ static function registerRoute($route) { - // TODO: Implement registerRoute() method. + $k = mb_substr($route['uri'], 1); + $v = Inflector::camel2id($k); + $url_manager = Yii::$app->urlManager; + $url_manager->rules[$k] = $v; + $url_manager->init(); } static function databaseQuery($sql) @@ -63,6 +89,6 @@ static function handleResponseJson($res) static function getTablePrefix() { - // TODO: Implement getTablePrefix() method. + return Yii::$app->db->tablePrefix; } } \ No newline at end of file From 57dc574089e3eac2705b44a70353967c0612e031 Mon Sep 17 00:00:00 2001 From: zzx Date: Sun, 12 May 2024 01:12:20 +0800 Subject: [PATCH 3/6] enh --- src/controllers/Yii2Controller.php | 100 +++++++++++++++++++++++++++++ src/providers/Yii2Service.php | 19 +++--- 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/src/controllers/Yii2Controller.php b/src/controllers/Yii2Controller.php index 31fbf9b..a861c20 100644 --- a/src/controllers/Yii2Controller.php +++ b/src/controllers/Yii2Controller.php @@ -3,6 +3,8 @@ namespace hg\apidoc\controllers; use hg\apidoc\Controller; +use Yii; +use yii\base\Event; use yii\filters\ContentNegotiator; use yii\web\Response; @@ -29,20 +31,118 @@ public function init($checkAuth = false) { $this->_ctrl = new Controller(); $this->_ctrl->init(); + + //给接口响应结果绑定beforeSend事件 + Yii::$app->getResponse()->on(Response::EVENT_BEFORE_SEND, [$this, 'beforeSend']); + } + + /** + * @param Event $event + */ + public function beforeSend(Event $event) + { + /* @var $response yii\web\Response */ + $response = $event->sender; + + //针对Yii2框架404默认返回的是html页面,rest没处理 + if ($response->statusCode === 404){ + $response->format = Response::FORMAT_JSON; + $response->data = [ + 'name' => $response->statusText, + 'message' => '请求不存在', + 'code' => 0, + 'status' => 404, + ]; + } } + /** + * 获取配置 + */ public function actionConfig() { return $this->_ctrl->getConfig(); } + /** + * 获取api文档菜单 + */ public function actionApiMenus() { return $this->_ctrl->getApiMenus(); } + /** + * 获取接口明细 + * @return array + */ + public function actionApiDetail() + { + return $this->_ctrl->getApiDetail(); + } + public function actionDocMenus() { return $this->_ctrl->getMdMenus(); } + + public function actionDocDetail() + { + return $this->_ctrl->getMdDetail(); + } + + public function actionVerifyAuth() + { + return $this->_ctrl->verifyAuth(); + } + + public function actionGenerator() + { + return $this->_ctrl->createGenerator(); + } + + public function actionCancelAllCache() + { + return $this->_ctrl->cancelAllCache(); + } + + public function actionCreateAllCache() + { + return $this->_ctrl->createAllCache(); + } + + public function actionRenderCodeTemplate() + { + return $this->_ctrl->renderCodeTemplate(); + } + + public function actionAllApiMenus() + { + return $this->_ctrl->getAllApiMenus(); + } + + public function actionAddApiShare() + { + return $this->_ctrl->addApiShare(); + } + + public function actionGetApiShareList() + { + return $this->_ctrl->getApiShareList(); + } + + public function actionGetApiShareDetail() + { + return $this->_ctrl->getApiShareDetail(); + } + + public function actionDeleteApiShare() + { + return $this->_ctrl->deleteApiShare(); + } + + public function actionHandleApiShareAction() + { + return $this->_ctrl->handleApiShareAction(); + } } \ No newline at end of file diff --git a/src/providers/Yii2Service.php b/src/providers/Yii2Service.php index 675b9e6..91495cd 100644 --- a/src/providers/Yii2Service.php +++ b/src/providers/Yii2Service.php @@ -5,6 +5,7 @@ use Yii; use yii\base\Component; use yii\base\InvalidConfigException; +use yii\db\Exception; use yii\helpers\Inflector; class Yii2Service extends Component @@ -48,38 +49,40 @@ static function getApidocConfig() */ static function registerRoute($route) { - $k = mb_substr($route['uri'], 1); - $v = Inflector::camel2id($k); + $rule_key = mb_substr($route['uri'], 1); + $rule_val = Inflector::camel2id($rule_key); $url_manager = Yii::$app->urlManager; - $url_manager->rules[$k] = $v; + $url_manager->rules[$rule_key] = $rule_val; $url_manager->init(); } + /** + * @throws Exception + */ static function databaseQuery($sql) { - // TODO: Implement databaseQuery() method. + Yii::$app->db->createCommand($sql)->execute(); } static function getRootPath() { //dd(Yii::getAlias('@app')); - return Yii::getAlias('@app'); + return Yii::getAlias('@app') . DIRECTORY_SEPARATOR; } static function getRuntimePath() { - //dd(Yii::getAlias('@runtime')); return Yii::getAlias('@runtime'); } static function setLang($locale) { - // TODO: Implement setLang() method. + Yii::$app->language = $locale; } static function getLang($lang) { - // TODO: Implement getLang() method. + return Yii::t('apidoc', mb_substr($lang, mb_strlen('apidoc.'))); } static function handleResponseJson($res) From 05eec8294fb251fab15aa832cf3804f10aea9c4f Mon Sep 17 00:00:00 2001 From: zzx Date: Sun, 12 May 2024 01:19:45 +0800 Subject: [PATCH 4/6] readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 63a7b50..a203470 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ ## 🤷‍♀️ Apidoc是什么? -Apidoc是一个通过解析注解生成Api接口文档的PHP composer扩展,兼容Laravel、ThinkPHP、Hyperf、Webman等框架; +Apidoc是一个通过解析注解生成Api接口文档的PHP composer扩展,兼容Laravel、ThinkPHP、Hyperf、Webman、Yii 等框架; 全面的注解引用、数据表字段引用,简单的注解即可生成Api文档,而Apidoc不仅于接口文档,在线接口调试、Mock调试数据、调试事件处理、Json/TypeScript生成、接口生成器、代码生成器等诸多实用功能,致力于提高Api接口开发效率。 @@ -51,6 +51,7 @@ Apidoc是一个通过解析注解生成Api接口文档的PHP composer扩展, |Laravel|8.x、9.x、10.x| |Webman|1.x| |Hyperf|2.x、3.x| +|Yii|2.x| ## 📖使用文档 From 00c54309daff44c2a69c1cd8c626b4dceb9b9f75 Mon Sep 17 00:00:00 2001 From: zzx Date: Sun, 12 May 2024 02:23:50 +0800 Subject: [PATCH 5/6] cfg enh --- src/providers/Yii2Service.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/providers/Yii2Service.php b/src/providers/Yii2Service.php index 91495cd..e301798 100644 --- a/src/providers/Yii2Service.php +++ b/src/providers/Yii2Service.php @@ -12,13 +12,19 @@ class Yii2Service extends Component { use BaseService; + /** + * apidoc 配置文件的完整路径 + * @var string + */ + public string $cfgPath = ''; + private static string $_cfg_path; + /** * @inheritdoc */ public function init() { - $this->initConfig(); - self::registerApidocRoutes(); + self::$_cfg_path = $this->cfgPath; Yii::$app->controllerMap = [ 'apidoc' => [ @@ -26,6 +32,9 @@ public function init() 'enableCsrfValidation' => false, ], ]; + + $this->initConfig(); + self::registerApidocRoutes(); } /** @@ -34,7 +43,7 @@ public function init() */ static function getApidocConfig() { - $config = require \Yii::getAlias('@common') . '/config/apidoc.php'; + $config = require self::$_cfg_path ?: __DIR__ . '/../config.php'; if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){ $config['auto_url']['filter_keys'] = ['app','controller']; From a5139d562ec00597746765292ed333ef0bdf2838 Mon Sep 17 00:00:00 2001 From: zzx Date: Mon, 13 May 2024 22:55:08 +0800 Subject: [PATCH 6/6] yii2 install doc --- src/docs/yii2_install.md | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/docs/yii2_install.md diff --git a/src/docs/yii2_install.md b/src/docs/yii2_install.md new file mode 100644 index 0000000..5dc1a35 --- /dev/null +++ b/src/docs/yii2_install.md @@ -0,0 +1,83 @@ +# Yii2 安装 + +> 在安装本插件时,确保你已成功安装 Yii2 的项目的 advanced 模板,并成功运行 +安装方法参考:[Yii2 文档](https://learnku.com/docs/yii-framework/2.0.x/advanced-install/11993) + +## 1、安装插件 + +进入项目根目录,执行如下命令: + +```shell +composer require hg/apidoc +``` + +## 2、配置文件 + +1、手动添加apidoc.php配置文件 + +手动将 `/vendor/hg/apidoc/src/config.php` 拷贝到`{app}/config/`目录下,并重命名为`apidoc.php` + + +2、Yii 2.x 版本需手动配置,让 Apidoc 在应用初始化时注册相关服务,如下: + +```php +// {app}/config/main.php 中 +return [ + // 增加 apidoc + 'bootstrap' => ['log', 'apidoc'], + // 组件中 + 'components' => [ + 'apidoc' => [ + 'class' => '\hg\apidoc\providers\Yii2Service', + 'cfgPath' => __DIR__ . '/apidoc.php' + ], + // 如果要支持国际化 + 'i18n' => [ + 'translations' => [ + '*' => [ + 'class' => 'yii\i18n\PhpMessageSource', + // {app}/messages/en-US/apidoc.php 英文 + // {app}/messages/zh-CN/apidoc.php 中文 + 'basePath' => '@app/messages', + 'sourceLanguage' => 'en-US', + 'fileMap' => [ + 'apidoc' => 'apidoc.php' + ], + ], + ], + ], + ], + // 如果要支持国际化 + 'sourceLanguage' => 'en-US',//源语言 + 'language' => 'zh-CN',//目标语言 +]; + +// {app}/messages/en-US/apidoc.php 中 + 'Test', +]; +``` + +> 根据项目结构调整 apps 配置 +```php +// {app}/config/apidoc.php +'apps' => [ + [ + 'title'=>'Api接口', + // (注意)核对配置文件中此目录是否正确 + 'path'=>'modules\v1\controllers', + 'key'=>'api', + ] +], +``` + +## 3、添加前端页面 + + + + +下载完成后解压,将apidoc文件夹拷贝到你的项目 web 目录下 + +打开浏览器访问 [http://你的域名/apidoc/](http://你的域名/apidoc/) ,出现接口文档页面,表示安装成功。 \ No newline at end of file