Api.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <?php
  2. namespace app\admin\command;
  3. use app\admin\command\Api\library\Builder;
  4. use think\Config;
  5. use think\console\Command;
  6. use think\console\Input;
  7. use think\console\input\Option;
  8. use think\console\Output;
  9. use think\Exception;
  10. class Api extends Command
  11. {
  12. protected function configure()
  13. {
  14. $site = Config::get('site');
  15. $this
  16. ->setName('api')
  17. ->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
  18. ->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
  19. ->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
  20. ->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
  21. ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
  22. ->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'] ?? '')
  23. ->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
  24. ->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
  25. ->addOption('addon', 'a', Option::VALUE_OPTIONAL, 'addon name', null)
  26. ->addOption('controller', 'r', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name', null)
  27. ->setDescription('Build Api document from controller');
  28. }
  29. protected function execute(Input $input, Output $output)
  30. {
  31. $apiDir = __DIR__ . DS . 'Api' . DS;
  32. $force = $input->getOption('force');
  33. $url = $input->getOption('url');
  34. $language = $input->getOption('language');
  35. $template = $input->getOption('template');
  36. if (!preg_match("/^([a-z0-9]+)\.html\$/i", $template)) {
  37. throw new Exception('template file not correct');
  38. }
  39. $language = $language ? $language : 'zh-cn';
  40. $langFile = $apiDir . 'lang' . DS . $language . '.php';
  41. if (!is_file($langFile)) {
  42. throw new Exception('language file not found');
  43. }
  44. $lang = include_once $langFile;
  45. // 目标目录
  46. $output_dir = ROOT_PATH . 'public' . DS;
  47. $output_file = $output_dir . $input->getOption('output');
  48. if (is_file($output_file) && !$force) {
  49. throw new Exception("api index file already exists!\nIf you need to rebuild again, use the parameter --force=true ");
  50. }
  51. // 模板文件
  52. $template_dir = $apiDir . 'template' . DS;
  53. $template_file = $template_dir . $template;
  54. if (!is_file($template_file)) {
  55. throw new Exception('template file not found');
  56. }
  57. // 额外的类
  58. $classes = $input->getOption('class');
  59. // 标题
  60. $title = $input->getOption('title');
  61. // 模块
  62. $module = $input->getOption('module');
  63. // 插件
  64. $addon = $input->getOption('addon');
  65. $moduleDir = $addonDir = '';
  66. if ($addon) {
  67. $addonInfo = get_addon_info($addon);
  68. if (!$addonInfo) {
  69. throw new Exception('addon not found');
  70. }
  71. $moduleDir = ADDON_PATH . $addon . DS;
  72. } else {
  73. $moduleDir = APP_PATH . $module . DS;
  74. }
  75. if (!is_dir($moduleDir)) {
  76. throw new Exception('module not found');
  77. }
  78. if (version_compare(PHP_VERSION, '7.0.0', '<')) {
  79. throw new Exception("Requires PHP version 7.0 or newer");
  80. }
  81. //控制器名
  82. $controller = $input->getOption('controller') ?: [];
  83. if (!$controller) {
  84. $controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
  85. $files = new \RecursiveIteratorIterator(
  86. new \RecursiveDirectoryIterator($controllerDir),
  87. \RecursiveIteratorIterator::LEAVES_ONLY
  88. );
  89. foreach ($files as $name => $file) {
  90. if (!$file->isDir() && $file->getExtension() == 'php') {
  91. $filePath = $file->getRealPath();
  92. $className = $this->getClassFromFile($filePath);
  93. if ($className) {
  94. $classes[] = $className;
  95. }
  96. }
  97. }
  98. } else {
  99. foreach ($controller as $index => $item) {
  100. $filePath = $moduleDir . Config::get('url_controller_layer') . DS . $item . '.php';
  101. $className = $this->getClassFromFile($filePath);
  102. if ($className) {
  103. $classes[] = $className;
  104. }
  105. }
  106. }
  107. $classes = array_unique(array_filter($classes));
  108. $config = [
  109. 'sitename' => config('site.name'),
  110. 'title' => $title,
  111. 'author' => config('site.name'),
  112. 'description' => '',
  113. 'apiurl' => $url,
  114. 'language' => $language,
  115. ];
  116. $builder = new Builder($classes);
  117. $content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
  118. if (!file_put_contents($output_file, $content)) {
  119. throw new Exception('Cannot save the content to ' . $output_file);
  120. }
  121. $output->info("Build Successed!");
  122. }
  123. /**
  124. * 从文件获取命名空间和类名
  125. *
  126. * @param string $filename
  127. * @return string
  128. */
  129. protected function getClassFromFile($filename)
  130. {
  131. $getNext = null;
  132. $isNamespace = false;
  133. $skipNext = false;
  134. $namespace = '';
  135. $class = '';
  136. foreach (\PhpToken::tokenize(file_get_contents($filename)) as $token) {
  137. if (!$token->isIgnorable()) {
  138. $name = $token->getTokenName();
  139. switch ($name) {
  140. case 'T_NAMESPACE':
  141. $isNamespace = true;
  142. break;
  143. case 'T_EXTENDS':
  144. case 'T_USE':
  145. case 'T_IMPLEMENTS':
  146. $skipNext = true;
  147. break;
  148. case 'T_CLASS':
  149. if ($skipNext) {
  150. $skipNext = false;
  151. } else {
  152. $getNext = strtolower(substr($name, 2));
  153. }
  154. break;
  155. case 'T_NAME_QUALIFIED':
  156. case 'T_NS_SEPARATOR':
  157. case 'T_STRING':
  158. case ';':
  159. if ($isNamespace) {
  160. if ($name == ';') {
  161. $isNamespace = false;
  162. } else {
  163. $namespace .= $token->text;
  164. }
  165. } elseif ($skipNext) {
  166. $skipNext = false;
  167. } elseif ($getNext == 'class') {
  168. $class = $token->text;
  169. $getNext = null;
  170. break 2;
  171. }
  172. break;
  173. default:
  174. $getNext = null;
  175. }
  176. }
  177. }
  178. $className = $namespace . '\\' . $class;
  179. return preg_match('/([a-z0-9_\\]+)([a-z0-9_]+)$/i', $className) ? $className : '';
  180. }
  181. }