├── .gitignore
├── src
├── config
│ └── plugin
│ │ └── hg
│ │ └── apidoc
│ │ ├── route.php
│ │ └── app.php
├── middleware
│ ├── LaravelMiddleware.php
│ ├── ThinkPHPMiddleware.php
│ ├── HyperfMiddleware.php
│ └── WebmanMiddleware.php
├── annotation
│ ├── NotParams.php
│ ├── NotQuerys.php
│ ├── NotResponses.php
│ ├── NotDefaultAuthor.php
│ ├── NotHeaders.php
│ ├── NotResponseError.php
│ ├── NotResponseSuccess.php
│ ├── NotDebug.php
│ ├── Group.php
│ ├── Sort.php
│ ├── Tag.php
│ ├── NotParse.php
│ ├── Url.php
│ ├── ContentType.php
│ ├── Field.php
│ ├── Author.php
│ ├── Desc.php
│ ├── Title.php
│ ├── WithoutField.php
│ ├── ParamType.php
│ ├── RouteMiddleware.php
│ ├── Method.php
│ ├── Md.php
│ ├── ResponseErrorMd.php
│ ├── ResponseSuccessMd.php
│ ├── ResponseStatus.php
│ ├── Header.php
│ ├── RouteParam.php
│ ├── EventBase.php
│ ├── ResponseError.php
│ ├── After.php
│ ├── ParamBase.php
│ ├── Param.php
│ ├── ResponseSuccess.php
│ ├── Before.php
│ ├── Query.php
│ ├── Property.php
│ ├── Returned.php
│ └── AddField.php
├── ConfigProvider.php
├── exception
│ ├── HttpException.php
│ └── ErrorException.php
├── utils
│ ├── ApiCrossDomain.php
│ ├── AbstractAnnotation.php
│ ├── Request.php
│ ├── Lang.php
│ ├── ConfigProvider.php
│ ├── AutoRegisterRouts.php
│ ├── ApiShare.php
│ ├── Cache.php
│ └── DirAndFile.php
├── providers
│ ├── CommonService.php
│ ├── WebmanService.php
│ ├── HyperfService.php
│ ├── ThinkPHP5Service.php
│ ├── LaravelService.php
│ ├── ThinkPHPService.php
│ └── BaseService.php
├── Install.php
├── parses
│ ├── ParseCodeTemplate.php
│ ├── ParseMarkdown.php
│ ├── ParseModel.php
│ ├── ParseAnnotation.php
│ └── ParseApiMenus.php
├── config.php
├── Auth.php
├── generator
│ ├── ParseTemplate.php
│ └── Index.php
└── Controller.php
├── LICENSE
├── composer.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
--------------------------------------------------------------------------------
/src/config/plugin/hg/apidoc/route.php:
--------------------------------------------------------------------------------
1 | all();
12 | $config = ConfigProvider::get();
13 | $config['request_params'] = $params;
14 | ConfigProvider::set($config);
15 | return $next($request);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/middleware/ThinkPHPMiddleware.php:
--------------------------------------------------------------------------------
1 | param();
12 | $config = ConfigProvider::get();
13 | $config['request_params'] = $params;
14 | ConfigProvider::set($config);
15 | return $next($request);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/annotation/NotParams.php:
--------------------------------------------------------------------------------
1 | [],
14 | 'publish' => [
15 | [
16 | 'id' => 'config',
17 | 'description' => 'The config of apidoc.',
18 | 'source' => __DIR__ . '/config.php',
19 | 'destination' => BASE_PATH . '/config/autoload/apidoc.php',
20 | ],
21 | ],
22 | ];
23 | }
24 | }
--------------------------------------------------------------------------------
/src/annotation/RouteMiddleware.php:
--------------------------------------------------------------------------------
1 | statusCode = $statusCode;
19 | $this->headers = $headers;
20 |
21 | parent::__construct($message, $code, $previous);
22 | }
23 |
24 | public function getStatusCode()
25 | {
26 | return $this->statusCode;
27 | }
28 |
29 | public function getHeaders()
30 | {
31 | return $this->headers;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/annotation/ResponseErrorMd.php:
--------------------------------------------------------------------------------
1 | server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : '';
12 | $response->header('Access-Control-Allow-Origin', $origin);
13 | $response->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN');
14 | $response->header('Access-Control-Expose-Headers', 'Authorization, authenticated');
15 | $response->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, OPTIONS');
16 | $response->header('Access-Control-Allow-Credentials', 'true');
17 | return $response;
18 |
19 | }
20 |
21 |
22 | }
--------------------------------------------------------------------------------
/src/utils/AbstractAnnotation.php:
--------------------------------------------------------------------------------
1 | formatParams($value);
17 | foreach ($formattedValue as $key => $val) {
18 | if ($key=="value" && !property_exists($this, $key)){
19 | $this->name = $val;
20 | }else{
21 | $this->{$key} = $val;
22 | }
23 | }
24 | }
25 |
26 | protected function formatParams($value): array
27 | {
28 | if (isset($value[0])) {
29 | $value = $value[0];
30 | }
31 | if (!is_array($value)) {
32 | $value = ['name' => $value];
33 | }
34 | return $value;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/annotation/ResponseStatus.php:
--------------------------------------------------------------------------------
1 | get = $_GET;
16 | $this->post = $_POST;
17 | $this->method = !empty($_SERVER['REQUEST_METHOD'])?$_SERVER['REQUEST_METHOD']:"";
18 | }
19 |
20 | public function get(){
21 | return $this->get;
22 | }
23 |
24 | public function post(){
25 | return $this->post;
26 | }
27 |
28 | public function input(){
29 | $input = file_get_contents('php://input');
30 | $inputObj = json_decode($input);
31 | return Helper::objectToArray($inputObj);
32 | }
33 |
34 | public function param(){
35 | $config = ConfigProvider::get();
36 | if (!empty($config['request_params'])){
37 | return $config['request_params'];
38 | }
39 | $method = !empty($this->method)?$this->method:"GET";
40 | if ($method == "GET"){
41 | return $this->get;
42 | }
43 | return $this->input();
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/src/annotation/RouteParam.php:
--------------------------------------------------------------------------------
1 | 'config/plugin/hg/apidoc',
15 | );
16 |
17 | /**
18 | * Install
19 | * @return void
20 | */
21 | public static function install()
22 | {
23 | foreach (static::$configPath as $source => $dest) {
24 | if ($pos = strrpos($dest, '/')) {
25 | $parent_dir = base_path() . '/' . substr($dest, 0, $pos);
26 | if (!is_dir($parent_dir)) {
27 | mkdir($parent_dir, 0777, true);
28 | }
29 | }
30 | //symlink(__DIR__ . "/$source", base_path()."/$dest");
31 | copy_dir(__DIR__ . "/$source", base_path() . "/$dest");
32 | echo "Create $dest";
33 | }
34 | }
35 |
36 | /**
37 | * Uninstall
38 | * @return void
39 | */
40 | public static function uninstall()
41 | {
42 | foreach (static::$configPath as $source => $dest) {
43 | $path = base_path()."/$dest";
44 | if (!is_dir($path) && !is_file($path)) {
45 | continue;
46 | }
47 | echo "Remove $dest";
48 | if (is_file($path) || is_link($path)) {
49 | unlink($path);
50 | continue;
51 | }
52 | remove_dir($path);
53 | }
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/src/annotation/After.php:
--------------------------------------------------------------------------------
1 | 1 ? trim($key[1]):"";
33 | if (!empty($langKey)){
34 | return $langGetFunction($langKey);
35 | }
36 | }
37 | }
38 | return $string;
39 | }
40 |
41 | /**
42 | * 二维数组设置指定字段的多语言
43 | * @param $array
44 | * @param $field
45 | * @return array
46 | */
47 | public static function getArrayLang($array,$field,$config=[]){
48 | $data = [];
49 | if (!empty($array) && is_array($array)){
50 | foreach ($array as $item){
51 | $item[$field] = static::getLang($item[$field],$config);
52 | $data[]=$item;
53 | }
54 | }
55 | return $data;
56 | }
57 |
58 |
59 | }
--------------------------------------------------------------------------------
/src/annotation/Before.php:
--------------------------------------------------------------------------------
1 | middleware([WebmanMiddleware::class]);
19 | });
20 |
21 | // 自动注册路由
22 | CommonService::autoRegisterRoutes(function ($routeData){
23 | foreach ($routeData as $controller) {
24 | if (count($controller['methods'])){
25 | $methods= $controller['methods'];
26 | $routeCallback = function ()use ($methods){
27 | foreach ($methods as $method) {
28 | $apiMethods = Helper::handleApiMethod($method['method']);
29 | $route = Route::add([...$apiMethods,'OPTIONS'],$method['url'], $method['controller']."@".$method['name']);
30 | if (!empty($method['middleware'])){
31 | $route->middleware($method['middleware']);
32 | }
33 | }
34 | };
35 | $routeGroup = Route::group("",$routeCallback);
36 | if (!empty($controller['middleware'])){
37 | $routeGroup->middleware($controller['middleware']);
38 | }
39 | }
40 | }
41 | }, WebmanMiddleware::getApidocConfig());
42 | }
43 | }
--------------------------------------------------------------------------------
/src/annotation/AddField.php:
--------------------------------------------------------------------------------
1 | [HyperfMiddleware::class]]);
26 | });
27 |
28 | // 自动注册路由
29 | CommonService::autoRegisterRoutes(function ($routeData){
30 | foreach ($routeData as $controller) {
31 | if (count($controller['methods'])){
32 | $methods= $controller['methods'];
33 | $routeCallback = function ()use ($methods){
34 | foreach ($methods as $method) {
35 | $apiMethods = Helper::handleApiMethod($method['method']);
36 | $options = [];
37 | if (!empty($method['middleware'])){
38 | $options['middleware']= $method['middleware'];
39 | }
40 | Router::addRoute([...$apiMethods,'OPTIONS'],$method['url'], $method['controller']."@".$method['name'],$options);
41 | }
42 | };
43 | $groupOptions = [];
44 | if (!empty($controller['middleware'])){
45 | $groupOptions['middleware'] = $controller['middleware'];
46 | }
47 | Router::addGroup("",$routeCallback,$groupOptions);
48 | }
49 | }
50 | }, HyperfMiddleware::getApidocConfig());
51 | }
52 | }
--------------------------------------------------------------------------------
/src/middleware/HyperfMiddleware.php:
--------------------------------------------------------------------------------
1 | initConfig();
22 |
23 | if ($request->getMethod() == "GET"){
24 | $params = $request->getQueryParams();
25 | }else{
26 | $params = $request->getParsedBody();
27 | }
28 | $config = ConfigProvider::get();
29 | $config['request_params'] = $params;
30 | ConfigProvider::set($config);
31 | return $handler->handle($request);
32 | }
33 |
34 | static function getApidocConfig()
35 | {
36 | $config = config("apidoc");
37 | $exportConfig = config("apidoc-export");
38 | if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
39 | $config['auto_url']['filter_keys'] = ['App','Controller'];
40 | }
41 | $config['app_frame'] = "hyperf";
42 | if (!empty($exportConfig)){
43 | $config['export_config'] = $exportConfig;
44 | }
45 | return $config;
46 | }
47 |
48 | static function registerRoute($route)
49 | {
50 | // TODO: Implement registerRoute() method.
51 | }
52 |
53 | static function databaseQuery($sql)
54 | {
55 | return Db::select($sql);
56 | }
57 |
58 | static function getRootPath()
59 | {
60 | return BASE_PATH."/";
61 | }
62 |
63 | static function getRuntimePath()
64 | {
65 | return BASE_PATH."/runtime/";
66 | }
67 |
68 | static function setLang($locale)
69 | {
70 | static::$langLocale = $locale;
71 | }
72 |
73 | static function getLang($lang): string
74 | {
75 | return trans($lang);
76 | }
77 |
78 | static function handleResponseJson($res)
79 | {
80 | return $res;
81 | }
82 |
83 | static function getTablePrefix(){
84 | return config('databases.default.prefix','');
85 | }
86 | }
--------------------------------------------------------------------------------
/src/middleware/WebmanMiddleware.php:
--------------------------------------------------------------------------------
1 | initConfig();
19 | $params = $request->all();
20 | $config = ConfigProvider::get();
21 | $config['request_params'] = $params;
22 | ConfigProvider::set($config);
23 |
24 | $response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
25 | if (!empty($config['allowCrossDomain'])){
26 | // 给响应添加跨域相关的http头
27 | $response->withHeaders([
28 | 'Access-Control-Allow-Credentials' => 'true',
29 | 'Access-Control-Allow-Origin' => $request->header('origin', '*'),
30 | 'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
31 | 'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
32 | ]);
33 | }
34 |
35 | return $response;
36 | }
37 |
38 | static function getApidocConfig()
39 | {
40 | $config = config('plugin.hg.apidoc.app.apidoc');
41 | $exportConfig = config('plugin.hg.apidoc-export.app');
42 | if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
43 | $config['auto_url']['filter_keys'] = ['app','controller'];
44 | }
45 | $config['app_frame'] = "webman";
46 | if (!empty($exportConfig)){
47 | $config['export_config'] = $exportConfig;
48 | }
49 | return $config;
50 | }
51 |
52 | static function registerRoute($route)
53 | {
54 | return "";
55 | }
56 |
57 | static function databaseQuery($sql)
58 | {
59 | return Db::select($sql);
60 | }
61 |
62 | static function getRootPath()
63 | {
64 | return BASE_PATH."/";
65 | }
66 |
67 | static function getRuntimePath()
68 | {
69 | return BASE_PATH."/runtime/";
70 | }
71 |
72 | static function setLang($locale)
73 | {
74 | locale($locale);
75 | }
76 |
77 | static function getLang($lang): string
78 | {
79 | return $lang;
80 | }
81 |
82 | static function handleResponseJson($res)
83 | {
84 | return json($res);
85 | }
86 |
87 | static function getTablePrefix(){
88 | $driver = config('database.default');
89 | $table_prefix=config('database.connections.'.$driver.'.prefix');
90 | return $table_prefix;
91 | }
92 | }
--------------------------------------------------------------------------------
/src/providers/ThinkPHP5Service.php:
--------------------------------------------------------------------------------
1 | initConfig();
19 | self::registerApidocRoutes();
20 | // 自动注册路由
21 | self::autoRegisterRoutes(function ($routeData){
22 | $appRoute = app('route');
23 | $routeGroup = $appRoute->getGroup();
24 | foreach ($routeData as $controller) {
25 | $routeGroup = $appRoute->getGroup();
26 | if (!empty($controller['middleware'])){
27 | $routeGroup->middleware($controller['middleware']);
28 | }
29 | if (count($controller['methods'])){
30 | foreach ($controller['methods'] as $method) {
31 | $apiMethods = Helper::handleApiMethod($method['method']);
32 | $apiMethods = implode("|",$apiMethods);
33 | $route = $routeGroup->addRule($method['url'],$method['controller']."@".$method['name'],$apiMethods);
34 | if (!empty($method['middleware'])){
35 | $route->middleware($method['middleware']);
36 | }
37 | }
38 | }
39 | }
40 | });
41 | }
42 |
43 | static function getApidocConfig()
44 | {
45 | $config = config("apidoc.");
46 | $exportConfig = config("apidoc-export.");
47 | if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
48 | $config['auto_url']['filter_keys'] = ['app','controller'];
49 | }
50 | $config['app_frame'] = "thinkphp5";
51 | if (!empty($exportConfig)){
52 | $config['export_config'] = $exportConfig;
53 | }
54 | return $config;
55 | }
56 |
57 | static function registerRoute($route){
58 | $config = self::getApidocConfig();
59 | $registerRoute = Route::rule($route['uri'], $route['callback'],"*");
60 | if (!empty($config['allowCrossDomain'])) {
61 | $registerRoute->allowCrossDomain();
62 | }
63 | }
64 |
65 | static function databaseQuery($sql){
66 | return Db::query($sql);
67 | }
68 |
69 | static function getTablePrefix(){
70 | $driver = config('database.default');
71 | $table_prefix=config('database.connections.'.$driver.'.prefix');
72 | return $table_prefix;
73 | }
74 |
75 | static function getRootPath()
76 | {
77 | return App::getRootPath();
78 | }
79 |
80 | static function getRuntimePath()
81 | {
82 | return App::getRuntimePath();
83 | }
84 |
85 | static function setLang($locale){
86 | Lang::setLangCookieVar($locale);
87 | }
88 |
89 | static function getLang($lang){
90 | return Lang::get($lang);
91 | }
92 |
93 | static function handleResponseJson($res){
94 | return json($res);
95 | }
96 |
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 |
6 | Apidoc
7 |
8 |
9 |
10 | 基于PHP的注解生成API文档及Api接口开发工具
11 |
12 |
13 |
14 |

15 |

16 |

17 |

18 |

19 |

20 |

21 |
22 |
23 |
24 |
25 | ## 🤷♀️ Apidoc是什么?
26 |
27 | Apidoc是一个通过解析注解生成Api接口文档的PHP composer扩展,兼容Laravel、ThinkPHP、Hyperf、Webman等框架;
28 | 全面的注解引用、数据表字段引用,简单的注解即可生成Api文档,而Apidoc不仅于接口文档,在线接口调试、Mock调试数据、调试事件处理、Json/TypeScript生成、接口生成器、代码生成器等诸多实用功能,致力于提高Api接口开发效率。
29 |
30 |
31 | ## ✨特性
32 |
33 | - 开箱即用:无繁杂的配置、安装后按文档编写注释即可自动生成API文档。
34 | - 轻松编写:支持通用注释引用、业务逻辑层、数据表字段的引用,几句注释即可完成。
35 | - 在线调试:在线文档可直接调试,并支持全局请求/Mock参数/事件处理,接口调试省时省力。
36 | - 安全高效:支持访问密码验证、应用/版本独立密码;支持文档缓存。
37 | - 多应用/多版本:可适应各种单应用、多应用、多版本的项目的Api管理。
38 | - 分组/Tag:可对控制器/接口进行多级分组或定义Tag。
39 | - Markdown文档:支持.md文件的文档展示。
40 | - Json/TypeScript生成:文档自动生成接口的Json及TypeScript。
41 | - 代码生成器:配置+模板即可快速生成代码及数据表的创建,大大提高工作效率。
42 | - 接口分享:支持自由指定应用/接口生成分享链接、导出swagger.json文件。
43 |
44 | ## 📌兼容
45 |
46 | 以下框架已内置兼容,可开箱即用
47 |
48 | | 框架 | 版本 | 说明 |
49 | | -------- |--------| ------------------------------------ |
50 | | ThinkPHP | \>=5.1 | |
51 | | Webman | \>=1.x | |
52 | | Laravel | \>=8.x | 低于 Laravel8 版本未测试,可自行尝试 |
53 | | Hyperf | \>=2.x | |
54 |
55 |
56 | ## 📖使用文档
57 |
58 | [https://docs.apidoc.icu](https://docs.apidoc.icu/)
59 |
60 |
61 | ## 🏆支持我们
62 |
63 | 如果本项目对您有所帮助,请点个Star支持我们
64 |
65 | - [Github](https://github.com/HGthecode/apidoc-php) ->
66 |
67 |
68 | - [Gitee](https://gitee.com/hg-code/apidoc-php) ->
69 |
70 |
71 | ## 🌐交流群
72 |
73 | 
74 |
75 |
76 |
77 | ## 💡鸣谢
78 |
79 | doctrine/annotations
80 |
81 |
82 | ## 🔗链接
83 | ApiDoc UI
84 |
85 | ApiDoc Demo
86 |
87 |
88 |
--------------------------------------------------------------------------------
/src/parses/ParseCodeTemplate.php:
--------------------------------------------------------------------------------
1 | config = $config;
25 | }
26 |
27 | public function renderCode($params)
28 | {
29 | $appKey = $params['appKey'];
30 | $currentAppConfig = Helper::getCurrentAppConfig($appKey);
31 | $currentApp = $currentAppConfig['appConfig'];
32 | $this->currentApp = $currentApp;
33 |
34 | $codeTemplate = $params['template'];
35 |
36 | //验证参数
37 |
38 | //验证模板文件是否存在
39 |
40 | //解析接口数据
41 | $tplData = [];
42 | if ($codeTemplate['select_mode'] == 'controller'){
43 | $parseApiMenusService = new ParseApiMenus($this->config);
44 | $controllers = $params['selected'];
45 | if (!empty($controllers) && count($controllers) > 0) {
46 | $controllerList = [];
47 | foreach ($controllers as $class) {
48 | $classData = $parseApiMenusService->parseController($class);
49 | if ($classData !== false) {
50 | $controllerList[] = $classData;
51 | }
52 | }
53 | if (empty($codeTemplate['multiple'])){
54 | $tplData = $controllerList[0];
55 | }else{
56 | $tplData = $controllerList;
57 | }
58 | }
59 | }else{
60 | // api
61 | $apis = $params['selected'];
62 | if (!empty($apis) && count($apis) > 0) {
63 | $parseApiDetailService = new ParseApiDetail($this->config);
64 | $apiList = [];
65 | foreach ($apis as $key) {
66 | $apiKey = urldecode($key);
67 | $apiDetail = $parseApiDetailService->renderApiDetail($appKey,$apiKey);
68 | if ($apiDetail !== false) {
69 | $apiList[] = $apiDetail;
70 | }
71 | }
72 | if (empty($codeTemplate['multiple'])){
73 | $tplData = $apiList[0];
74 | }else{
75 | $tplData = $apiList;
76 | }
77 | }
78 | }
79 |
80 |
81 | // 读取模板
82 | $templatePath =DirAndFile::formatPath( APIDOC_ROOT_PATH . $codeTemplate['template'],"/");
83 | if (is_readable($templatePath) == false) {
84 | throw new ErrorException("template not found", [
85 | 'template' => $template
86 | ]);
87 | }
88 | $tplParams = [
89 | 'form'=> $params['form'],
90 | 'data'=>$tplData
91 | ];
92 | $html = (new ParseTemplate())->compile($templatePath,$tplParams);
93 |
94 |
95 |
96 | return $html;
97 | }
98 |
99 |
100 | }
--------------------------------------------------------------------------------
/src/exception/ErrorException.php:
--------------------------------------------------------------------------------
1 | ['status'=>404,'code' => 4004, 'msg' => '文档已关闭'],
15 | 'password error' => ['status'=>402,'code' => 4002, 'msg' => '密码不正确,请重新输入'],
16 | 'password not found' => ['status'=>402,'code' => 4002, 'msg' => '密码不可为空'],
17 | 'token error' => ['status'=>401,'code' => 4001, 'msg' => '不合法的Token'],
18 | 'token not found' => ['status'=>401,'code' => 4001, 'msg' => '不存在Token'],
19 | 'appkey not found' => ['status'=>412,'code' => 4005, 'msg' => '缺少必要参数appKey'],
20 | 'mdPath not found' => ['status'=>412,'code' => 4006, 'msg' => '缺少必要参数path'],
21 | 'appKey error' => ['status'=>412,'code' => 4007, 'msg' => '不存在 key为${appKey}的apps配置'],
22 | 'template not found' => ['status'=>412,'code' => 4008, 'msg' => '${template}模板不存在'],
23 | 'path not found' => ['status'=>412,'code' => 4009, 'msg' => '${path}目录不存在'],
24 | 'classname error' => ['status'=>412,'code' => 4010, 'msg' => '${classname}文件名不合法'],
25 | 'apiKey not found' => ['status'=>412,'code' => 4011, 'msg' => '缺少必要参数apiKey'],
26 | 'no config apps' => ['status'=>412,'code' => 5000, 'msg' => 'apps配置不可为空'],
27 | 'unknown error' => ['status'=>501,'code' => 5000, 'msg' => '未知异常'],
28 | 'no debug' => ['status'=>412,'code' => 5001, 'msg' => '请在debug模式下,使用该功能'],
29 | 'no config crud' => ['status'=>412,'code' => 5002, 'msg' => 'crud未配置'],
30 | 'datatable crud error' => ['status'=>412,'code' => 5003, 'msg' => '数据表创建失败,请检查配置'],
31 | 'file already exists' => ['status'=>412,'code' => 5004, 'msg' => '${filepath}文件已存在'],
32 | 'file not exists' => ['status'=>412,'code' => 5005, 'msg' => '${filepath}文件不存在'],
33 | 'datatable already exists' => ['status'=>412,'code' => 5004, 'msg' => '数据表${table}已存在'],
34 | 'datatable not exists' => ['status'=>412,'code' => 5004, 'msg' => '数据表${table}不存在'],
35 | 'ref file not exists' => ['status'=>412,'code' => 5005, 'msg' => 'ref引入 ${path} 文件不存在'],
36 | 'ref method not exists' => ['status'=>412,'code' => 5005, 'msg' => 'ref引入${path} 中 ${method} 方法不存在'],
37 | 'datatable create error' => ['status'=>412,'code' => 5006, 'msg' => '数据表[${table}]创建失败,error:${message},sql:${sql}'],
38 | 'field not found' => ['status'=>412,'code' => 5006, 'msg' => '${field}字段不能为空'],
39 | 'share not exists' => ['status'=>404,'code' => 4004, 'msg' => '该分享不存在'],
40 | 'export config not enable' => ['status'=>404,'code' => 4004, 'msg' => '导出配置未启用'],
41 | ];
42 |
43 | public function __construct(string $exceptionCode, array $data = [])
44 | {
45 | $config = ConfigProvider::get();
46 | $exception = $this->getException($exceptionCode);
47 | if ($exception){
48 | $msg = Helper::replaceTemplate($exception['msg'], $data);
49 | }else{
50 | $exception = $this->exceptions['unknown error'];
51 | $msg = $exceptionCode;
52 | }
53 | parent::__construct($exception['status'], $msg, null, [], $exception['code']);
54 |
55 | }
56 |
57 | public function getException($exceptionCode)
58 | {
59 | if (isset($this->exceptions[$exceptionCode])) {
60 | return $this->exceptions[$exceptionCode];
61 | }
62 | return null;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/config.php:
--------------------------------------------------------------------------------
1 | 'Apidoc',
5 | // (选配)文档描述,显示在首页
6 | 'desc' => '',
7 | // (必须)设置文档的应用/版本
8 | 'apps' => [
9 | [
10 | // (必须)标题
11 | 'title'=>'Api接口',
12 | // (必须)控制器目录地址
13 | 'path'=>'app\controller',
14 | // (必须)唯一的key
15 | 'key'=>'api',
16 | ]
17 | ],
18 |
19 | // (必须)指定通用注释定义的文件地址
20 | 'definitions' => "app\common\controller\Definitions",
21 | // (必须)自动生成url规则,当接口不添加@Apidoc\Url ("xxx")注解时,使用以下规则自动生成
22 | 'auto_url' => [
23 | // 字母规则,lcfirst=首字母小写;ucfirst=首字母大写;
24 | 'letter_rule' => "lcfirst",
25 | // url前缀
26 | 'prefix'=>"",
27 | ],
28 | // 是否自动注册路由
29 | 'auto_register_routes'=>false,
30 | // (必须)缓存配置
31 | 'cache' => [
32 | // 是否开启缓存
33 | 'enable' => false,
34 | ],
35 | // (必须)权限认证配置
36 | 'auth' => [
37 | // 是否启用密码验证
38 | 'enable' => false,
39 | // 全局访问密码
40 | 'password' => "123456",
41 | // 密码加密盐
42 | 'secret_key' => "apidoc#hg_code",
43 | // 授权访问后的有效期
44 | 'expire' => 24*60*60
45 | ],
46 | // 全局参数
47 | 'params'=>[
48 | // (选配)全局的请求Header
49 | 'header'=>[
50 | // name=字段名,type=字段类型,require=是否必须,default=默认值,desc=字段描述
51 | ['name'=>'Authorization','type'=>'string','require'=>true,'desc'=>'身份令牌Token'],
52 | ],
53 | // (选配)全局的请求Query
54 | 'query'=>[
55 | // 同上 header
56 | ],
57 | // (选配)全局的请求Body
58 | 'body'=>[
59 | // 同上 header
60 | ],
61 | ],
62 | // 全局响应体
63 | 'responses'=>[
64 | // 成功响应体
65 | 'success'=>[
66 | ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1],
67 | ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
68 | //参数同上 headers;main=true来指定接口Returned参数挂载节点
69 | ['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object','require'=>1],
70 | ],
71 | // 异常响应体
72 | 'error'=>[
73 | ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1,'md'=>'/docs/HttpError.md'],
74 | ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
75 | ]
76 | ],
77 | // (选配)全局响应状态码
78 | 'responses_status'=>[
79 | [
80 | 'name'=>'200',
81 | 'desc'=>'请求成功'
82 | ],
83 | [
84 | 'name'=>'401',
85 | 'desc'=>'登录令牌无效',
86 | 'contentType'=>''
87 | ],
88 | ],
89 | // (选配)apidoc路由前缀,默认apidoc
90 | 'route_prefix'=>'/apidoc',
91 | //(选配)默认作者
92 | 'default_author'=>'',
93 | //(选配)默认请求类型
94 | 'default_method'=>'GET',
95 | //(选配)允许跨域访问
96 | 'allowCrossDomain'=>false,
97 | /**
98 | * (选配)解析时忽略带@注解的关键词,当注解中存在带@字符并且非Apidoc注解,如 @key test,此时Apidoc页面报类似以下错误时:
99 | * [Semantical Error] The annotation "@key" in method xxx() was never imported. Did you maybe forget to add a "use" statement for this annotation?
100 | */
101 | 'ignored_annitation'=>[],
102 |
103 | // (选配)解析时忽略的方法
104 | 'ignored_methods'=>[],
105 |
106 | // (选配)数据库配置
107 | 'database'=>[],
108 | // (选配)Markdown文档
109 | 'docs' => [],
110 | // (选配)代码生成器配置 注意:是一个二维数组
111 | 'generator' =>[],
112 | // (选配)代码模板
113 | 'code_template'=>[],
114 | // (选配)接口分享功能
115 | 'share'=>[
116 | // 是否开启接口分享功能
117 | 'enable'=>false,
118 | // 自定义接口分享操作,二维数组,每个配置为一个按钮操作
119 | 'actions'=>[]
120 | ],
121 | ];
122 |
--------------------------------------------------------------------------------
/src/providers/LaravelService.php:
--------------------------------------------------------------------------------
1 | publishes([
23 | __DIR__.'/../config.php' => config_path('apidoc.php'),
24 | ]);
25 | }
26 |
27 | public function register()
28 | {
29 | $config = static::getApidocConfig();
30 | $this->initConfig();
31 | self::registerApidocRoutes();
32 |
33 | // 自动注册路由
34 | self::autoRegisterRoutes(function ($routeData){
35 | foreach ($routeData as $controller) {
36 | if (count($controller['methods'])){
37 | $methods= $controller['methods'];
38 | $routeCallback = function ()use ($methods){
39 | foreach ($methods as $method) {
40 | $apiMethods = Helper::handleApiMethod($method['method']);
41 | $route = Route::match($apiMethods + ['OPTIONS'],$method['url'], "\\".$method['controller']."@".$method['name']);
42 | if (!empty($method['middleware'])){
43 | $route->middleware($method['middleware']);
44 | }
45 | }
46 | };
47 | $routeGroup = Route::prefix("");
48 | if (!empty($controller['middleware'])){
49 | $routeGroup->middleware($controller['middleware']);
50 | }
51 | $routeGroup->group($routeCallback);
52 | }
53 | }
54 | });
55 |
56 | }
57 |
58 | static function getApidocConfig()
59 | {
60 | $config = config("apidoc");
61 | $exportConfig = config("apidoc-export");
62 | if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
63 | $config['auto_url']['filter_keys'] = ['App','Http','Controllers'];
64 | }
65 | $config['app_frame'] = "laravel";
66 | if (!empty($exportConfig)){
67 | $config['export_config'] = $exportConfig;
68 | }
69 | return $config;
70 | }
71 |
72 | static function registerRoute($route){
73 | $config = self::getApidocConfig();
74 | $registerRoute = Route::any($route['uri'], $route['callback']);
75 | $registerRoute->middleware([LaravelMiddleware::class]);
76 | if (!empty($config['allowCrossDomain'])) {
77 | $registerRoute->middleware([ApiCrossDomain::class]);
78 | }
79 | }
80 |
81 | static function databaseQuery($sql){
82 | return DB::select($sql);
83 | }
84 |
85 | static function getTablePrefix(){
86 | $driver = config('database.default');
87 | $table_prefix=config('database.connections.'.$driver.'.prefix');
88 | return $table_prefix;
89 | }
90 |
91 | static function getRootPath()
92 | {
93 | return base_path()."/";
94 | }
95 |
96 | static function getRuntimePath()
97 | {
98 | return storage_path()."/";
99 | }
100 |
101 | static function setLang($locale){
102 | Lang::setLocale($locale);
103 | }
104 |
105 | static function getLang($lang){
106 | return trans($lang);
107 | }
108 |
109 | static function handleResponseJson($res){
110 | return $res;
111 | }
112 |
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/config/plugin/hg/apidoc/app.php:
--------------------------------------------------------------------------------
1 | true,
4 | 'apidoc' => [
5 | // (选配)文档标题,显示在左上角与首页
6 | 'title' => 'Apidoc',
7 | // (选配)文档描述,显示在首页
8 | 'desc' => '',
9 | // (必须)设置文档的应用/版本
10 | 'apps' => [
11 | [
12 | // (必须)标题
13 | 'title'=>'Api接口',
14 | // (必须)控制器目录地址
15 | 'path'=>'app\controller',
16 | // (必须)唯一的key
17 | 'key'=>'api',
18 | ]
19 | ],
20 | // (必须)指定通用注释定义的文件地址
21 | 'definitions' => "app\common\controller\Definitions",
22 | // (必须)自动生成url规则,当接口不添加@Apidoc\Url ("xxx")注解时,使用以下规则自动生成
23 | 'auto_url' => [
24 | // 字母规则,lcfirst=首字母小写;ucfirst=首字母大写;
25 | 'letter_rule' => "lcfirst",
26 | // url前缀
27 | 'prefix'=>"",
28 | ],
29 | // (选配)是否自动注册路由
30 | 'auto_register_routes'=>false,
31 | // (必须)缓存配置
32 | 'cache' => [
33 | // 是否开启缓存
34 | 'enable' => false,
35 | ],
36 | // (必须)权限认证配置
37 | 'auth' => [
38 | // 是否启用密码验证
39 | 'enable' => false,
40 | // 全局访问密码
41 | 'password' => "123456",
42 | // 密码加密盐
43 | 'secret_key' => "apidoc#hg_code",
44 | // 授权访问后的有效期
45 | 'expire' => 24*60*60
46 | ],
47 | // 全局参数
48 | 'params'=>[
49 | // (选配)全局的请求Header
50 | 'header'=>[
51 | // name=字段名,type=字段类型,require=是否必须,default=默认值,desc=字段描述
52 | ['name'=>'Authorization','type'=>'string','require'=>true,'desc'=>'身份令牌Token'],
53 | ],
54 | // (选配)全局的请求Query
55 | 'query'=>[
56 | // 同上 header
57 | ],
58 | // (选配)全局的请求Body
59 | 'body'=>[
60 | // 同上 header
61 | ],
62 | ],
63 | // 全局响应体
64 | 'responses'=>[
65 | // 成功响应体
66 | 'success'=>[
67 | ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1],
68 | ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
69 | //参数同上 headers;main=true来指定接口Returned参数挂载节点
70 | ['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object','require'=>1],
71 | ],
72 | // 异常响应体
73 | 'error'=>[
74 | ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1,'md'=>'/docs/HttpError.md'],
75 | ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
76 | ]
77 | ],
78 | // (选配)全局响应状态码
79 | 'responses_status'=>[
80 | [
81 | 'name'=>'200',
82 | 'desc'=>'请求成功'
83 | ],
84 | [
85 | 'name'=>'401',
86 | 'desc'=>'登录令牌无效',
87 | 'contentType'=>''
88 | ],
89 | ],
90 | //(选配)默认作者
91 | 'default_author'=>'',
92 | //(选配)默认请求类型
93 | 'default_method'=>'GET',
94 | //(选配)Apidoc允许跨域访问
95 | 'allowCrossDomain'=>false,
96 | /**
97 | * (选配)解析时忽略带@注解的关键词,当注解中存在带@字符并且非Apidoc注解,如 @key test,此时Apidoc页面报类似以下错误时:
98 | * [Semantical Error] The annotation "@key" in method xxx() was never imported. Did you maybe forget to add a "use" statement for this annotation?
99 | */
100 | 'ignored_annitation'=>[],
101 |
102 | // (选配)解析时忽略的方法
103 | 'ignored_methods'=>[],
104 |
105 | // (选配)数据库配置
106 | 'database'=>[],
107 | // (选配)Markdown文档
108 | 'docs' => [],
109 | // (选配)接口生成器配置 注意:是一个二维数组
110 | 'generator' =>[]
111 | ]
112 | ];
113 |
--------------------------------------------------------------------------------
/src/providers/ThinkPHPService.php:
--------------------------------------------------------------------------------
1 | initConfig();
35 |
36 | $this->registerRoutes(function () use($config){
37 | //注册apidoc所需路由
38 | self::registerApidocRoutes(function ($route)use ($config){
39 | $registerRoute = Route::any($route['uri'], $route['callback']);
40 | $registerRoute->middleware([ThinkPHPMiddleware::class]);
41 | if (!empty($config['allowCrossDomain'])) {
42 | $registerRoute->allowCrossDomain();
43 | }
44 | });
45 |
46 | // 自动注册路由
47 | self::autoRegisterRoutes(function ($routeData){
48 | $appRoute = $this->app->route;
49 | $appName = $this->app->http->getName();
50 | foreach ($routeData as $controller) {
51 | $routeGroup = $appRoute->getGroup();
52 | // 这里注册的控制器中间件会对所有的路由生效(包括apidoc自身的路由)
53 | // if (!empty($controller['middleware'])){
54 | // $routeGroup->middleware($controller['middleware']);
55 | // }
56 | if (count($controller['methods'])){
57 | foreach ($controller['methods'] as $method) {
58 | if (!empty($appName)){
59 | $method['url'] = str_replace("/".$appName,'',$method['url']);
60 | }
61 | $apiMethods = Helper::handleApiMethod($method['method']);
62 | $apiMethods = implode("|",$apiMethods);
63 | $route = $routeGroup->addRule($method['url'],$method['controller']."@".$method['name'],$apiMethods);
64 | // 将控制器中间件注册到控制器下的所有路由下,避免影响全局路由
65 | if (!empty($controller['middleware'])){
66 | $route->middleware($controller['middleware']);
67 | }
68 | if (!empty($method['middleware'])){
69 | $route->middleware($method['middleware']);
70 | }
71 | }
72 | }
73 | }
74 | });
75 | });
76 | }
77 |
78 | static function registerRoute($route){
79 | $registerRoute = Route::any($route['uri'], $route['callback']);
80 | }
81 |
82 | static function databaseQuery($sql){
83 | return Db::query($sql);
84 | }
85 |
86 | static function getTablePrefix(){
87 | $driver = config('database.default');
88 | $table_prefix=config('database.connections.'.$driver.'.prefix');
89 | return $table_prefix;
90 | }
91 |
92 | static function getRootPath()
93 | {
94 | return App::getRootPath();
95 | }
96 |
97 | static function getRuntimePath()
98 | {
99 | return App::getRuntimePath();
100 | }
101 |
102 | static function setLang($locale){
103 | \think\facade\App::loadLangPack($locale);
104 | Lang::setLangSet($locale);
105 | }
106 |
107 | static function getLang($lang){
108 | return Lang::get($lang);
109 | }
110 |
111 | static function handleResponseJson($res){
112 | return json($res);
113 | }
114 |
115 |
116 | }
--------------------------------------------------------------------------------
/src/utils/ConfigProvider.php:
--------------------------------------------------------------------------------
1 | [
12 | 'folder'=>'apidoc'
13 | ]
14 | ];
15 | protected static $config = [];
16 |
17 |
18 | public static function get($field=""){
19 |
20 | if (!empty(static::$config)) {
21 | $config = static::$config;
22 | }else{
23 | throw new ErrorException('ConfigProvider get error');
24 | }
25 |
26 | return Helper::getObjectFindByField($config,$field);
27 | }
28 |
29 | public static function set($config){
30 | if (!(!empty($config['cache']) && !empty($config['cache']['folder']))){
31 | if (!empty($config['cache'])){
32 | $config['cache']['folder'] =static::$defaultConfig['cache']['folder'];
33 | }
34 | }
35 | $config = static::handleConfig($config);
36 | static::$config = $config;
37 | }
38 |
39 | public static function handleConfig($config){
40 | if (!empty($config['params'])){
41 | if (!empty($config['params']['header'])){
42 | $config['params']['header'] = Helper::handleArrayParams($config['params']['header'],"desc",$config);
43 | }
44 | if (!empty($config['params']['query'])){
45 | $config['params']['query'] = Helper::handleArrayParams($config['params']['query'],"desc",$config);
46 | }
47 | if (!empty($config['params']['body'])){
48 | $config['params']['body'] = Helper::handleArrayParams($config['params']['body'],"desc",$config);
49 | }
50 | }
51 | if (!empty($config['responses'])){
52 | if (!empty($config['responses']['success'])){
53 | $config['responses']['success'] = Helper::handleArrayParams($config['responses']['success'],"desc",$config);
54 | }
55 | if (!empty($config['responses']['error'])){
56 | $config['responses']['error'] = Helper::handleArrayParams($config['responses']['error'],"desc",$config);
57 | }
58 | }
59 | if (!empty($config['title'])){
60 | $config['title'] = Lang::getLang($config['title'],$config);
61 | }
62 | return $config;
63 | }
64 |
65 | public static function getFeConfig($filterAppKeys=[]){
66 | $config = static::$config;
67 |
68 | $feConfig = [
69 | 'title' =>!empty($config['title'])?Lang::getLang($config['title'] ):'',
70 | 'desc' =>!empty($config['title'])?Lang::getLang($config['desc']):'',
71 | 'apps'=>!empty($config['apps'])?$config['apps']:[],
72 | 'cache'=>!empty($config['cache'])?$config['cache']:[],
73 | 'params'=>!empty($config['params'])?$config['params']:[],
74 | 'responses'=>!empty($config['responses'])?$config['responses']:[],
75 | 'generator'=>!empty($config['generator'])?$config['generator']:[],
76 | 'code_template'=>!empty($config['code_template'])?$config['code_template']:[],
77 | 'share'=>!empty($config['share'])?$config['share']:[],
78 | 'export_config'=>!empty($config['export_config'])?$config['export_config']:[],
79 | ];
80 | if (!empty($feConfig['apps']) && count($feConfig['apps'])){
81 | // 清除apps配置中的password
82 | $feConfig['apps'] = Helper::handleAppsConfig($feConfig['apps'],true,"","",$filterAppKeys);
83 | }
84 |
85 | if (!empty($feConfig['generator'])){
86 | $generatorList = [];
87 | $generators= Helper::handleArrayParams($feConfig['generator'],"title");
88 | foreach ($generators as $item) {
89 | if (isset($item['enable']) && $item['enable'] === false){
90 | continue;
91 | }
92 | if (!empty($item['form']) && !empty($item['form']['items']) && count($item['form']['items'])){
93 | $item['form']['items'] = Helper::handleArrayParams( $item['form']['items'],"title");
94 | }
95 | $generatorList[]=$item;
96 | }
97 | $feConfig['generator'] = $generatorList;
98 | }
99 | return $feConfig;
100 | }
101 |
102 |
103 | }
--------------------------------------------------------------------------------
/src/parses/ParseMarkdown.php:
--------------------------------------------------------------------------------
1 | config = $config;
18 | }
19 |
20 | /**
21 | * 获取md文档菜单
22 | * @return array
23 | */
24 | public function getDocsMenu($appKey,string $lang): array
25 | {
26 | $config = $this->config;
27 | $docData = [];
28 | if (!empty($config['docs']) && count($config['docs']) > 0) {
29 | $docData = $this->handleDocsMenuData($config['docs'],$appKey,$lang);
30 | }
31 | return $docData;
32 | }
33 |
34 | /**
35 | * 处理md文档菜单数据
36 | * @param array $menus
37 | * @return array
38 | */
39 | protected function handleDocsMenuData(array $menus,$appKey,string $lang): array
40 | {
41 | $list = [];
42 | foreach ($menus as $item) {
43 | $item['title'] = Lang::getLang($item['title']);
44 | if (!empty($item['appKey']) && $item['appKey'] != $appKey){
45 | continue;
46 | }
47 |
48 | if (!empty($item['children']) && count($item['children']) > 0) {
49 | $item['children'] = $this->handleDocsMenuData($item['children'],$appKey,$lang);
50 | $item['menuKey'] = Helper::createRandKey("md_group");
51 | } else {
52 | $filePath = static::getFilePath($appKey,$item['path'],$lang);
53 | if (!file_exists($filePath['filePath'])) {
54 | continue;
55 | }
56 |
57 | if(!empty($item['path'])){
58 | $item['path'] = Helper::replaceTemplate($item['path'],['lang'=>$lang]);
59 | }
60 | $item['type'] = 'md';
61 | $item['menuKey'] = Helper::createApiKey($item['path']);
62 | }
63 | $list[] = $item;
64 | }
65 | return $list;
66 | }
67 |
68 | public static function getFilePath(string $appKey, string $path,$lang=""){
69 | if (!empty($appKey)){
70 | $currentAppConfig = Helper::getCurrentAppConfig($appKey);
71 | $currentApps = $currentAppConfig['apps'];
72 | $fullPath = Helper::replaceCurrentAppTemplate($path, $currentApps);
73 | }else{
74 | $fullPath = $path;
75 | }
76 | $fullPath = Helper::replaceTemplate($fullPath,[
77 | 'lang'=>$lang
78 | ]);
79 |
80 | if (strpos($fullPath, '#') !== false) {
81 | $mdPathArr = explode("#", $fullPath);
82 | $mdPath=$mdPathArr[0];
83 | $mdAnchor =$mdPathArr[1];
84 | } else {
85 | $mdPath = $fullPath;
86 | $mdAnchor="";
87 | }
88 | $fileSuffix = "";
89 | if (strpos($fullPath, '.md') === false) {
90 | $fileSuffix = ".md";
91 | }
92 | $filePath = APIDOC_ROOT_PATH . $mdPath . $fileSuffix;
93 | return [
94 | 'filePath'=>$filePath,
95 | 'anchor'=>$mdAnchor
96 | ];
97 | }
98 |
99 |
100 | /**
101 | * 获取md文档内容
102 | * @param string $appKey
103 | * @param string $path
104 | * @return string
105 | */
106 | public static function getContent(string $appKey, string $path,$lang="")
107 | {
108 | $filePathArr = static::getFilePath($appKey,$path,$lang);
109 | $mdAnchor = $filePathArr['anchor'];
110 | $filePath = $filePathArr['filePath'];
111 | if (!file_exists($filePath)) {
112 | return $path;
113 | }
114 | $contents = DirAndFile::getFileContent($filePath);
115 | // 获取指定h2标签内容
116 | if (!empty($mdAnchor)){
117 | if (strpos($contents, '## ') !== false) {
118 | $contentArr = explode("\r\n", $contents);
119 | $contentText = "";
120 | foreach ($contentArr as $line){
121 | $contentText.="\r\n".trim($line);
122 | }
123 | $contentArr = explode("\r\n## ", $contentText);
124 | $content="";
125 | foreach ($contentArr as $item){
126 | $itemArr = explode("\r\n", $item);
127 | if (!empty($itemArr) && $itemArr[0] && $mdAnchor===$itemArr[0]){
128 | $content = str_replace($itemArr[0]."\r\n", '', $item);
129 | break;
130 | }
131 | }
132 | return $content;
133 | }
134 | }
135 | return $contents;
136 | }
137 |
138 |
139 | }
--------------------------------------------------------------------------------
/src/utils/AutoRegisterRouts.php:
--------------------------------------------------------------------------------
1 | config = $config;
29 | }
30 |
31 | /**
32 | * 解析所有应用的api
33 | * @return array
34 | */
35 | public function getAppsApis(){
36 | $apps = Helper::getAllApps($this->config['apps']);
37 | $apiList = [];
38 | if (!empty($apps) && count($apps)){
39 | foreach ($apps as $app) {
40 | $apis = $this->getAppApis($app);
41 | $apiList=array_merge($apiList,$apis);
42 | }
43 | }
44 | return $apiList;
45 |
46 | }
47 |
48 | /**
49 | * 生成api接口数据
50 | * @param array $app
51 | * @return array
52 | */
53 | public function getAppApis($app)
54 | {
55 | $controllers = [];
56 | if (!empty($app['controllers']) && count($app['controllers']) > 0) {
57 | // 配置的控制器列表
58 | $controllers = (new ParseApiMenus($this->config))->getConfigControllers($app['path'],$app['controllers']);
59 | }else if(!empty($app['path']) && is_array($app['path']) && count($app['path'])){
60 | $parseApiMenus = new ParseApiMenus($this->config);
61 | foreach ($app['path'] as $path) {
62 | $controllersList = $parseApiMenus->getDirControllers($path);
63 | $controllers = array_merge($controllers,$controllersList);
64 | }
65 | } else if(!empty($app['path']) && is_string($app['path'])) {
66 | // 默认读取所有的
67 | $controllers = (new ParseApiMenus($this->config))->getDirControllers($app['path']);
68 | }
69 |
70 | $routeData = [];
71 | if (!empty($controllers) && count($controllers) > 0) {
72 | foreach ($controllers as $class) {
73 | $classData = $this->parseController($class);
74 | if ($classData !== false) {
75 | $routeData[] = $classData;
76 | }
77 | }
78 | }
79 | return $routeData;
80 | }
81 |
82 |
83 | public function parseController($class)
84 | {
85 | $refClass = new ReflectionClass($class);
86 | $classTextAnnotations = ParseAnnotation::parseTextAnnotation($refClass);
87 | $classAnnotations = (new ParseAnnotation($this->config))->getClassAnnotation($refClass);
88 | if (in_array("NotParse", $classTextAnnotations) || isset($classAnnotations['notParse'])) {
89 | return false;
90 | }
91 |
92 |
93 | $methodList = [];
94 | foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
95 | $methodItem = $this->parseApiMethod($refClass,$refMethod);
96 | if ($methodItem===false){
97 | continue;
98 | }
99 | $methodList[] = $methodItem;
100 | }
101 | if (count($methodList)===0){
102 | return false;
103 | }
104 | $data = [
105 | 'name'=>$refClass->name,
106 | 'methods'=>$methodList,
107 | ];
108 |
109 | //控制器中间件
110 | if (!empty($classAnnotations['routeMiddleware']) && !empty($classAnnotations['routeMiddleware'])) {
111 | $data['middleware'] = $classAnnotations['routeMiddleware'];
112 | }
113 | return $data;
114 | }
115 |
116 |
117 | protected function parseApiMethod($refClass,$refMethod){
118 | if (empty($refMethod->name) || in_array($refMethod->name,$this->filterMethods)) {
119 | return false;
120 | }
121 | $config = $this->config;
122 | $textAnnotations = ParseAnnotation::parseTextAnnotation($refMethod);
123 | $methodAnnotation = (new ParseAnnotation($config))->getMethodAnnotation($refMethod);
124 | if (in_array("NotParse", $textAnnotations) || isset($methodAnnotation['notParse'])) {
125 | return false;
126 | }
127 |
128 | if (empty($methodAnnotation['method'])) {
129 | $method = !empty($config['default_method']) ? strtoupper($config['default_method']) : '*';
130 | }else{
131 | $method = $methodAnnotation['method'];
132 | }
133 | if (empty($methodAnnotation['url'])) {
134 | $url = ParseApiDetail::autoCreateUrl($refClass->name,$refMethod->name,$config);
135 | }else{
136 | $url = $methodAnnotation['url'];
137 | }
138 | if (!empty($url) && substr($url, 0, 1) != "/") {
139 | $url = "/" . $url;
140 | }
141 | $data = [
142 | 'url'=>$url,
143 | 'method'=>$method,
144 | 'name'=>$refMethod->name,
145 | 'controller'=>$refClass->name,
146 | ];
147 | if (!empty($methodAnnotation['routeMiddleware']) && !empty($methodAnnotation['routeMiddleware'])) {
148 | $data['middleware'] = $methodAnnotation['routeMiddleware'];
149 | }
150 | return $data;
151 |
152 | }
153 |
154 | }
--------------------------------------------------------------------------------
/src/providers/BaseService.php:
--------------------------------------------------------------------------------
1 | 'config','route'=>'getConfig'],
15 | ['rule'=>'apiMenus','route'=>'getApiMenus'],
16 | ['rule'=>'apiDetail','route'=>'getApiDetail'],
17 | ['rule'=>'docMenus','route'=>'getMdMenus'],
18 | ['rule'=>'docDetail','route'=>'getMdDetail'],
19 | ['rule'=>'verifyAuth','route'=>'verifyAuth'],
20 | ['rule'=>'generator','route'=>'createGenerator'],
21 | ['rule'=>'cancelAllCache','route'=>'cancelAllCache'],
22 | ['rule'=>'createAllCache','route'=>'createAllCache'],
23 | ['rule'=>'renderCodeTemplate','route'=>'renderCodeTemplate'],
24 | ['rule'=>'allApiMenus','route'=>'getAllApiMenus'],
25 | ['rule'=>'addApiShare','route'=>'addApiShare'],
26 | ['rule'=>'getApiShareList','route'=>'getApiShareList'],
27 | ['rule'=>'getApiShareDetail','route'=>'getApiShareDetail'],
28 | ['rule'=>'deleteApiShare','route'=>'deleteApiShare'],
29 | ['rule'=>'handleApiShareAction','route'=>'handleApiShareAction'],
30 | ['rule'=>'exportSwagger','route'=>'exportSwagger'],
31 | ];
32 |
33 |
34 |
35 | /**
36 | * 获取apidoc配置
37 | * @return array 返回apidoc配置
38 | */
39 | abstract static function getApidocConfig();
40 |
41 |
42 | /**
43 | * 注册apidoc路由
44 | * @param $route 路由参数
45 | * @return mixed
46 | */
47 | abstract static function registerRoute($route);
48 |
49 | /**
50 | * 执行Sql语句
51 | * @return mixed
52 | */
53 | abstract static function databaseQuery($sql);
54 |
55 | /**
56 | * 获取项目根目录
57 | * @return string 返回项目根目录
58 | */
59 | abstract static function getRootPath();
60 |
61 | /**
62 | * 获取缓存目录
63 | * @return string 返回项目缓存目录
64 | */
65 | abstract static function getRuntimePath();
66 |
67 |
68 | /**
69 | * 设置当前语言
70 | * @param $locale 语言标识
71 | * @return mixed
72 | */
73 | abstract static function setLang($locale);
74 |
75 | /**
76 | * 获取语言定义
77 | * @param $lang
78 | * @return string
79 | */
80 | abstract static function getLang($lang);
81 |
82 |
83 | /**
84 | * 处理apidoc接口响应的数据
85 | * @return mixed
86 | */
87 | abstract static function handleResponseJson($res);
88 |
89 | abstract static function getTablePrefix();
90 |
91 | // 自动注册api路由
92 | static public function autoRegisterRoutes($routeFun,$config=""){
93 | if (empty($config)){
94 | $config = self::getApidocConfig();
95 | }
96 | if (isset($config['auto_register_routes']) && $config['auto_register_routes']===true) {
97 | $cacheKey = "apis/autoRegisterRoutes";
98 | if (!empty($config['cache']) && $config['cache']['enable']) {
99 | $cacheData = (new Cache())->get($cacheKey);
100 | if (!empty($cacheData)) {
101 | $autoRegisterApis = $cacheData;
102 | } else {
103 | $autoRegisterApis = (new AutoRegisterRouts($config))->getAppsApis();
104 | (new Cache())->set($cacheKey, $autoRegisterApis);
105 | }
106 | } else {
107 | $autoRegisterApis = (new AutoRegisterRouts($config))->getAppsApis();
108 | }
109 | $routeFun($autoRegisterApis);
110 | }
111 | }
112 |
113 | public function initConfig(){
114 | ! defined('APIDOC_ROOT_PATH') && define('APIDOC_ROOT_PATH', $this->getRootPath());
115 | ! defined('APIDOC_STORAGE_PATH') && define('APIDOC_STORAGE_PATH', $this->getRuntimePath());
116 | $config = self::getApidocConfig();
117 | if (empty($config['database_query_function'])){
118 | $config['database_query_function'] = function ($sql){
119 | return self::databaseQuery($sql);
120 | };
121 | }
122 | if (empty($config['lang_register_function'])){
123 | $config['lang_register_function'] = function ($sql){
124 | return self::setLang($sql);
125 | };
126 | }
127 | if (empty($config['lang_get_function'])){
128 | $config['lang_get_function'] = function ($lang){
129 | return self::getLang($lang);
130 | };
131 | }
132 | $config['handle_response_json'] = function ($res){
133 | return self::handleResponseJson($res);
134 | };
135 | $table_prefix = self::getTablePrefix();
136 | if (!empty($config['database'])){
137 | if (empty($config['prefix'])){
138 | $config['database']['prefix'] = $table_prefix;
139 | }
140 | }else{
141 | $config['database']=[
142 | 'prefix'=>$table_prefix
143 | ];
144 | }
145 | ConfigProvider::set($config);
146 | }
147 |
148 | /**
149 | * @param null $routeFun
150 | */
151 | static public function registerApidocRoutes($routeFun=null){
152 | $routes = static::$routes;
153 | $controller_namespace = '\hg\apidoc\Controller@';
154 | $route_prefix = "/apidoc";
155 | $config = self::getApidocConfig();
156 | if (!empty($config) && !empty($config['route_prefix'])){
157 | $route_prefix = $config['route_prefix'];
158 | }
159 | foreach ($routes as $item) {
160 | $route = [
161 | 'uri'=>$route_prefix."/".$item['rule'],
162 | 'callback'=>$controller_namespace.$item['route'],
163 | 'route'=>$item['route'],
164 | ];
165 | if (!empty($routeFun)){
166 | $routeFun($route);
167 | }else{
168 | self::registerRoute($route);
169 | }
170 | }
171 | }
172 |
173 | }
--------------------------------------------------------------------------------
/src/utils/ApiShare.php:
--------------------------------------------------------------------------------
1 | $params['name'],
29 | 'type' => $params['type'],
30 | ];
31 | if (!empty($params['appKeys'])) {
32 | $data['appKeys'] = $params['appKeys'];
33 | }
34 | if (!empty($params['apiKeys'])) {
35 | $data['apiKeys'] = $params['apiKeys'];
36 | }
37 | if (!empty($params['password'])) {
38 | $data['password'] = $params['password'];
39 | }
40 | $data['create_at'] = date('Y-m-d h:i:s');
41 | $data['create_time'] = time();
42 | $res = (new Cache())->set($cacheKey, $data);
43 | return $res;
44 | }
45 |
46 | public function getSharePageList($config,$pageIndex,$pageSize){
47 | $path = APIDOC_STORAGE_PATH . $config['cache']['folder'] . "/share";
48 |
49 | $list = DirAndFile::getFileList($path);
50 | $data = [];
51 | $cache = new Cache();
52 |
53 | foreach ($list as $item) {
54 | $fileNameArr = explode("_", $item['name']);
55 | $cacheKey = "share/" . $fileNameArr[0] . "_" . $fileNameArr[1];
56 | $cacheData = $cache->get($cacheKey);
57 | $itemData = [
58 | 'key' => $fileNameArr[1],
59 | 'name' => $cacheData['name'],
60 | 'type' => $cacheData['type'],
61 | 'create_time' => $cacheData['create_time'],
62 | 'create_at' => $cacheData['create_at'],
63 | ];
64 | $data[] = $itemData;
65 | }
66 | $data = Helper::arraySortByKey($data, 'create_time', SORT_DESC);
67 | $page = $pageIndex-1;
68 | $res = array_slice($data,$page*$pageSize,$pageSize);
69 | return [
70 | 'total'=>count($data),
71 | 'data'=>$res
72 | ];
73 | }
74 |
75 |
76 | public function getShareDetailByKey($shareKey)
77 | {
78 | if (empty($shareKey)) {
79 | throw new ErrorException('field not found', ['field' => 'shareKey']);
80 | }
81 | $cache = new Cache();
82 | $cacheKey = static::getShareCacheKey($shareKey);
83 | $cacheData = $cache->get($cacheKey);
84 | if (empty($cacheData)) {
85 | throw new ErrorException("share not exists");
86 | }
87 | return $cacheData;
88 | }
89 |
90 | public function checkShareAuth($config, $params)
91 | {
92 | if (empty($params['shareKey'])) {
93 | throw new ErrorException('field not found', ['field' => 'shareKey']);
94 | }
95 | $cacheData = $this->getShareDetailByKey($params['shareKey']);
96 |
97 | if (!empty($cacheData['password'])) {
98 | //验证密码
99 | if (empty($params['token'])) {
100 | throw new ErrorException("token not found");
101 | }
102 | $password = md5($cacheData['password']);
103 | $cacheData['pass'] = $password;
104 | $checkAuth = (new Auth($config))->checkToken($params['token'], $password);
105 | if (!$checkAuth) {
106 | throw new ErrorException("token error");
107 | }
108 | }
109 | return $cacheData;
110 | }
111 |
112 | public static function getAppShareApis(array $config, array $apps, $parentKey = "", $filterAppKeys = [], $isParseDetail = false)
113 | {
114 | $appList = [];
115 | $separ = !empty($parentKey) ? ',' : '';
116 | foreach ($apps as $app) {
117 | $appKey = $parentKey . $separ . $app['key'];
118 | if (!empty($app['items']) && count($app['items'])) {
119 | $items = static::getAppShareApis($config, $app['items'], $appKey, $filterAppKeys, $isParseDetail);
120 | $app['children'] = $items;
121 | } else {
122 | $app['appKey'] = $appKey;
123 | $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey, $isParseDetail);
124 | $app['children'] = $apiData['data'];
125 | }
126 | if (!empty($filterAppKeys) && count($filterAppKeys) && !in_array($appKey, $filterAppKeys) && empty($app['items'])) {
127 | continue;
128 | }
129 | $app['menuKey'] = $appKey;
130 | $appList[] = $app;
131 | }
132 |
133 | return $appList;
134 | }
135 |
136 | public function getShareData($config, $key)
137 | {
138 | $shareData = $this->getShareDetailByKey($key);
139 | $filterAppKeys = !empty($shareData['appKeys']) ? $shareData['appKeys'] : [];
140 | $configApps = Helper::handleAppsConfig($config['apps'], false, $config);
141 | $appList = static::getAppShareApis($config, $configApps, "", $filterAppKeys, true);
142 | if ($shareData['type'] == 'api') {
143 | $appList = Helper::filterTreeNodesByKeys($appList, $shareData['apiKeys'], 'menuKey');
144 | }
145 | return [
146 | 'shareData' => $shareData,
147 | 'apiData' => $appList,
148 | ];
149 | }
150 |
151 | public function handleApiShareAction($config, $key, $index)
152 | {
153 |
154 | $actionConfig = $config['share']['actions'][$index];
155 | if (!empty($actionConfig['click'])) {
156 | $data = $this->getShareData($config, $key);
157 | $res = $actionConfig['click']($data['shareData'], $data['apiData']);
158 | return $res;
159 | }
160 | return false;
161 |
162 | }
163 |
164 |
165 | }
--------------------------------------------------------------------------------
/src/parses/ParseModel.php:
--------------------------------------------------------------------------------
1 | config = $config;
22 | }
23 |
24 | public function parseModelTable($model, $classReflect, $methodName = "")
25 | {
26 | if (!is_callable(array($model, 'getTable'))) {
27 | return false;
28 | }
29 | $config = $this->config;
30 | try {
31 | // 获取所有模型属性
32 | $propertys = $classReflect->getDefaultProperties();
33 | $tableName = $model->getTable();
34 | $configTablePrefix = !empty($config['database']) && !empty($config['database']['prefix']) ? $config['database']['prefix'] : "";
35 | if (!empty($configTablePrefix) && strpos($tableName, $configTablePrefix) === false) {
36 | $tableName = $configTablePrefix . $model->getTable();
37 | }
38 | $table = $this->getTableDocument($tableName, $propertys, $model);
39 | if (empty($methodName)) {
40 | return $table;
41 | }
42 |
43 | $methodReflect = $classReflect->getMethod($methodName);
44 | $annotations = (new ParseAnnotation($config))->getMethodAnnotation($methodReflect);
45 | if (!empty($annotations['field'])) {
46 | $table = ParseApiDetail::filterParamsField($table, $annotations['field'], 'field');
47 | }
48 | if (!empty($annotations['withoutField'])) {
49 | $table = ParseApiDetail::filterParamsField($table, $annotations['withoutField'], 'withoutField');
50 | }
51 | if (!empty($annotations['addField'])) {
52 | $addFieldData = [];
53 | if (is_int(Helper::arrayKeyFirst($annotations['addField']))) {
54 | $addFieldData = $annotations['addField'];
55 | } else {
56 | $addFieldData = [$annotations['addField']];
57 | }
58 | $addFieldList = [];
59 | $parseApiDetail = new ParseApiDetail($config);
60 | $field = 'param';
61 | foreach ($addFieldData as $fieldItem) {
62 | if (!empty($fieldItem['ref'])) {
63 | $refParams = $parseApiDetail->renderRef($fieldItem['ref'], $field);
64 | if (!empty($refParams[$field])) {
65 | $fieldItem = $parseApiDetail->handleRefData($fieldItem, $refParams[$field], $field);
66 | }
67 | }
68 | if (!empty($fieldItem['md'])) {
69 | $fieldItem['md'] = ParseMarkdown::getContent("", $fieldItem['md']);
70 | }
71 | // 自定义解析
72 | if (!empty($config['parsesAnnotation'])) {
73 | $callback = $config['parsesAnnotation']($fieldItem);
74 | if (!empty($callback)) {
75 | $fieldItem = $callback;
76 | }
77 | }
78 | $addFieldList[] = $fieldItem;
79 | }
80 | $table = Helper::arrayMergeAndUnique("name", $table, $addFieldList);
81 | }
82 | return $table;
83 | } catch (\ReflectionException $e) {
84 | throw new ErrorException('Class ' . get_class($model) . ' ' . $e->getMessage());
85 | }
86 |
87 | }
88 |
89 | /**
90 | * 获取模型实例
91 | * @param $method
92 | * @return mixed|null
93 | */
94 | public static function getModelClass($namespaceName)
95 | {
96 | if (!empty($namespaceName) && class_exists($namespaceName)) {
97 | $modelInstance = new $namespaceName();
98 | if (is_callable(array($modelInstance, 'getTable'))) {
99 | return $modelInstance;
100 | }
101 | }
102 | return null;
103 | }
104 |
105 |
106 | /**
107 | * 获取模型注解数据
108 | * @param $tableName
109 | * @param $propertys
110 | * @return array
111 | */
112 | public function getTableDocument($tableName, array $propertys, $model = null): array
113 | {
114 | $config = $this->config;
115 | $fieldComment = [];
116 | if (empty($config['database_query_function'])) {
117 | throw new ErrorException("not datatable_query_function config");
118 | }
119 | $tableColumns = $config['database_query_function']("SHOW FULL COLUMNS FROM `" . $tableName . "`");
120 | foreach ($tableColumns as $columns) {
121 | $columns = Helper::objectToArray($columns);
122 | $name = $columns['Field'];
123 | $desc = $columns['Comment'];
124 | $mock = "";
125 | $md = "";
126 | if (isset($propertys['convertNameToCamel']) && $propertys['convertNameToCamel'] === true) {
127 | $name = Helper::camel($name);
128 | }
129 | if (!empty($desc)) {
130 | // 存在字段注释
131 | $desc = Lang::getLang($desc);
132 | if (strpos($desc, 'mock(') !== false) {
133 | // 存在mock
134 | preg_match('#mock\((.*)\)#s', $desc, $mocks);
135 | if (!empty($mocks[1])) {
136 | $mock = $mocks[1];
137 | $desc = str_replace($mocks[0], "", $desc);
138 | }
139 | }
140 | if (strpos($desc, 'md="') !== false) {
141 | // 存在md
142 | preg_match('#md="(.*)"#s', $desc, $mdRefs);
143 | if (!empty($mdRefs[1])) {
144 | $md = ParseMarkdown::getContent("", $mdRefs[1]);
145 | $desc = str_replace($mdRefs[0], "", $desc);
146 | }
147 | }
148 | }
149 | $fieldComment[] = [
150 | "name" => $name,
151 | "type" => $columns['Type'],
152 | "desc" => $desc,
153 | "default" => $columns['Default'],
154 | "require" => $columns['Null'] != "YES",
155 | "mock" => $mock,
156 | "md" => $md,
157 | ];
158 | }
159 | return $fieldComment;
160 | }
161 |
162 | }
--------------------------------------------------------------------------------
/src/Auth.php:
--------------------------------------------------------------------------------
1 | authConfig = $authConfig;
24 | }
25 |
26 | /**
27 | * 验证密码是否正确
28 | * @param $password
29 | * @return false|string
30 | */
31 | public function verifyAuth(string $password, string $appKey)
32 | {
33 | $authConfig = $this->authConfig;
34 | if (!empty($appKey)) {
35 | $currentAppConfig = Helper::getCurrentAppConfig($appKey);
36 | $currentApp = $currentAppConfig['appConfig'];
37 | if (!empty($currentApp) && !empty($currentApp['password'])) {
38 | // 应用密码
39 | if (md5($currentApp['password']) === $password) {
40 | return $this->createToken($currentApp['password'],$authConfig['expire']);
41 | }
42 | throw new ErrorException("password error");
43 | }
44 | }
45 | if ($authConfig['enable']) {
46 | // 密码验证
47 | if (md5($authConfig['password']) === $password) {
48 | return $this->createToken($authConfig['password'],$authConfig['expire']);
49 | }
50 | throw new ErrorException("password error");
51 | }
52 | return false;
53 | }
54 |
55 | /**
56 | * 验证token是否可用
57 | * @param $request
58 | * @return bool
59 | */
60 | public function checkAuth($params=[]): bool
61 | {
62 | $authConfig = $this->authConfig;
63 | $token = !empty($params['token'])?$params['token']:"";
64 | $appKey = !empty($params['appKey'])?$params['appKey']:"";
65 | if (!empty($appKey)) {
66 | $currentAppConfig = Helper::getCurrentAppConfig($appKey);
67 | $currentApp = $currentAppConfig['appConfig'];
68 | if (!empty($currentApp) && !empty($currentApp['password'])) {
69 | if (empty($token)) {
70 | throw new ErrorException("token not found");
71 | }
72 | // 应用密码
73 | if ($this->checkToken($token, $currentApp['password'])) {
74 | return true;
75 | } else {
76 | throw new ErrorException("token error");
77 | }
78 | } else if (empty($authConfig['enable'])) {
79 | return true;
80 | }
81 | }
82 | if($authConfig['enable'] && empty($token)){
83 | throw new ErrorException("token not found");
84 | }else if (!empty($token) && !$this->checkToken($token, "") && $authConfig['enable']) {
85 | throw new ErrorException("token error");
86 | }
87 | return true;
88 | }
89 |
90 |
91 | /**
92 | * 获取tokencode
93 | * @param string $password
94 | * @return string
95 | */
96 | protected static function getTokenCode(string $password): string
97 | {
98 | return md5(md5($password));
99 | }
100 |
101 |
102 | /**
103 | * 创建token
104 | * @param string $password
105 | * @return string
106 | */
107 | public function createToken(string $password): string
108 | {
109 | $authConfig = $this->authConfig;
110 | $data = [
111 | 'key'=>static::getTokenCode($password),
112 | 'expire'=>time()+$authConfig['expire']
113 | ];
114 | $code = json_encode($data);
115 | return static::handleToken($code, "CE",$authConfig['secret_key']);
116 | }
117 |
118 | /**
119 | * 验证token是否可用
120 | * @param $token
121 | * @return bool
122 | */
123 | public function checkToken(string $token, string $password): bool
124 | {
125 | $authConfig = $this->authConfig;
126 | if (empty($password)){
127 | $password = $authConfig['password'];
128 | }
129 | $decode = static::handleToken($token, "DE",$authConfig['secret_key']);
130 | $deData = json_decode($decode,true);
131 |
132 | if (!empty($deData['key']) && $deData['key'] === static::getTokenCode($password) && !empty($deData['expire']) && $deData['expire']>time()){
133 | return true;
134 | }
135 |
136 | return false;
137 | }
138 |
139 |
140 |
141 | /**
142 | * 处理token
143 | * @param $string
144 | * @param string $operation
145 | * @param string $key
146 | * @param int $expiry
147 | * @return false|string
148 | */
149 | protected static function handleToken(string $string, string $operation = 'DE', string $key = '', int $expiry = 0):string
150 | {
151 | $ckey_length = 4;
152 | $key = md5($key);
153 | $keya = md5(substr($key, 0, 16));
154 | $keyb = md5(substr($key, 16, 16));
155 | $keyc = $ckey_length ? ($operation == 'DE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
156 | $cryptkey = $keya . md5($keya . $keyc);
157 | $key_length = strlen($cryptkey);
158 | $string = $operation == 'DE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
159 | $string_length = strlen($string);
160 | $result = '';
161 | $box = range(0, 255);
162 | $rndkey = array();
163 | for ($i = 0; $i <= 255; $i++) {
164 | $rndkey[$i] = ord($cryptkey[$i % $key_length]);
165 | }
166 | for ($j = $i = 0; $i < 256; $i++) {
167 | $j = ($j + $box[$i] + $rndkey[$i]) % 256;
168 | $tmp = $box[$i];
169 | $box[$i] = $box[$j];
170 | $box[$j] = $tmp;
171 | }
172 | for ($a = $j = $i = 0; $i < $string_length; $i++) {
173 | $a = ($a + 1) % 256;
174 | $j = ($j + $box[$a]) % 256;
175 | $tmp = $box[$a];
176 | $box[$a] = $box[$j];
177 | $box[$j] = $tmp;
178 | $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
179 | }
180 | if ($operation == 'DE') {
181 | $subNumber = (int)substr($result, 0, 10);
182 | if (($subNumber == 0 || $subNumber - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
183 | return substr($result, 26);
184 | } else {
185 | return '';
186 | }
187 | } else {
188 | return $keyc . str_replace('=', '', base64_encode($result));
189 | }
190 | }
191 |
192 | }
--------------------------------------------------------------------------------
/src/utils/Cache.php:
--------------------------------------------------------------------------------
1 | 0,
32 | 'cache_subdir' => true,
33 | 'prefix' => '',
34 | 'path' => '',
35 | 'hash_type' => 'md5',
36 | 'data_compress' => false,
37 | 'serialize' => [],
38 | ];
39 |
40 | /**
41 | * 架构函数
42 | * @param array $options 参数
43 | */
44 | public function __construct( array $options = [])
45 | {
46 | if (!empty($options)) {
47 | $this->options = array_merge($this->options, $options);
48 | }
49 |
50 | if (empty($this->options['path'])) {
51 | $this->options['path'] = APIDOC_STORAGE_PATH .'/'. 'apidoc';
52 | }
53 |
54 | if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) {
55 | $this->options['path'] .= DIRECTORY_SEPARATOR;
56 | }
57 | }
58 |
59 | /**
60 | * 取得变量的存储文件名
61 | * @access public
62 | * @param string $name 缓存变量名
63 | * @return string
64 | */
65 | public function getCacheKey(string $name): string
66 | {
67 | $name = $name."_".hash($this->options['hash_type'], $name);
68 |
69 | if ($this->options['prefix']) {
70 | $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name;
71 | }
72 |
73 | return $this->options['path'] . $name . '.php';
74 | }
75 |
76 | /**
77 | * 序列化数据
78 | * @access protected
79 | * @param mixed $data 缓存数据
80 | * @return string
81 | */
82 | protected function serialize($data): string
83 | {
84 | if (is_numeric($data)) {
85 | return (string) $data;
86 | }
87 |
88 | $serialize = $this->options['serialize'][0] ?? "serialize";
89 |
90 | return $serialize($data);
91 | }
92 |
93 |
94 | /**
95 | * 反序列化数据
96 | * @access protected
97 | * @param string $data 缓存数据
98 | * @return mixed
99 | */
100 | protected function unserialize($data)
101 | {
102 | if (is_numeric($data)) {
103 | return $data;
104 | }
105 |
106 | $unserialize = $this->options['serialize'][1] ?? "unserialize";
107 |
108 | return $unserialize($data);
109 | }
110 |
111 |
112 | /**
113 | * 获取有效期
114 | * @access protected
115 | * @param integer|DateTimeInterface|DateInterval $expire 有效期
116 | * @return int
117 | */
118 | protected function getExpireTime($expire): int
119 | {
120 | if ($expire instanceof DateTimeInterface) {
121 | $expire = $expire->getTimestamp() - time();
122 | } elseif ($expire instanceof DateInterval) {
123 | $expire = DateTime::createFromFormat('U', (string) time())
124 | ->add($expire)
125 | ->format('U') - time();
126 | }
127 |
128 | return (int) $expire;
129 | }
130 |
131 | /**
132 | * 获取缓存数据
133 | * @param string $name 缓存标识名
134 | * @return array|null
135 | */
136 | protected function getRaw(string $name)
137 | {
138 | $filename = $this->getCacheKey($name);
139 |
140 | if (!is_file($filename)) {
141 | return;
142 | }
143 |
144 | $content = @file_get_contents($filename);
145 |
146 | if (false !== $content) {
147 | $expire = (int) substr($content, 8, 12);
148 | $createTime = filemtime($filename);
149 | if (0 != $expire && time() - $expire > $createTime) {
150 | //缓存过期删除缓存文件
151 | DirAndFile::unlink($item->getPathname());
152 | return;
153 | }
154 |
155 | $content = substr($content, 32);
156 |
157 | if ($this->options['data_compress'] && function_exists('gzcompress')) {
158 | //启用数据压缩
159 | $content = gzuncompress($content);
160 | }
161 |
162 | return is_string($content) ? ['content' => $content, 'expire' => $expire,'create_time'=>$createTime] : null;
163 | }
164 | }
165 |
166 | /**
167 | * 判断缓存是否存在
168 | * @access public
169 | * @param string $name 缓存变量名
170 | * @return bool
171 | */
172 | public function has($name): bool
173 | {
174 | return $this->getRaw($name) !== null;
175 | }
176 |
177 | /**
178 | * 读取缓存
179 | * @access public
180 | * @param string $name 缓存变量名
181 | * @param mixed $default 默认值
182 | * @return mixed
183 | */
184 | public function get($name, $default = null)
185 | {
186 | $this->readTimes++;
187 |
188 | $raw = $this->getRaw($name);
189 |
190 | return is_null($raw) ? $default : $this->unserialize($raw['content']);
191 | }
192 |
193 | /**
194 | * 写入缓存
195 | * @access public
196 | * @param string $name 缓存变量名
197 | * @param mixed $value 存储数据
198 | * @param int|\DateTime $expire 有效时间 0为永久
199 | * @return bool
200 | */
201 | public function set($name, $value, $expire = null): bool
202 | {
203 | $this->writeTimes++;
204 |
205 | if (is_null($expire)) {
206 | $expire = $this->options['expire'];
207 | }
208 |
209 | $expire = $this->getExpireTime($expire);
210 | $filename = $this->getCacheKey($name);
211 |
212 | $dir = dirname($filename);
213 |
214 | if (!is_dir($dir)) {
215 | try {
216 | mkdir($dir, 0755, true);
217 | } catch (\Exception $e) {
218 | // 创建失败
219 | }
220 | }
221 |
222 | $data = $this->serialize($value);
223 |
224 | if ($this->options['data_compress'] && function_exists('gzcompress')) {
225 | //数据压缩
226 | $data = gzcompress($data, 3);
227 | }
228 |
229 | $data = "\n" . $data;
230 | $result = file_put_contents($filename, $data);
231 |
232 | if ($result) {
233 | clearstatcache();
234 | return true;
235 | }
236 |
237 | return false;
238 | }
239 |
240 |
241 |
242 | /**
243 | * 删除缓存
244 | * @access public
245 | * @param string $name 缓存变量名
246 | * @return bool
247 | */
248 | public function delete($name): bool
249 | {
250 | $this->writeTimes++;
251 |
252 | return DirAndFile::unlink($this->getCacheKey($name));
253 | }
254 |
255 | /**
256 | * 清除缓存
257 | * @access public
258 | * @return bool
259 | */
260 | public function clear(): bool
261 | {
262 | $this->writeTimes++;
263 |
264 | $dirname = $this->options['path'] . $this->options['prefix'];
265 |
266 | $this->rmdir($dirname);
267 |
268 | return true;
269 | }
270 |
271 |
272 | /**
273 | * 删除文件夹
274 | * @param $dirname
275 | * @return bool
276 | */
277 | private function rmdir($dirname)
278 | {
279 | if (!is_dir($dirname)) {
280 | return false;
281 | }
282 |
283 | $items = new FilesystemIterator($dirname);
284 |
285 | foreach ($items as $item) {
286 | if ($item->isDir() && !$item->isLink()) {
287 | $this->rmdir($item->getPathname());
288 | } else {
289 | DirAndFile::unlink($item->getPathname());
290 | }
291 | }
292 |
293 | @rmdir($dirname);
294 |
295 | return true;
296 | }
297 |
298 | }
299 |
--------------------------------------------------------------------------------
/src/parses/ParseAnnotation.php:
--------------------------------------------------------------------------------
1 | parser = new AnnotationReader();
22 | if (!empty($config['ignored_annitation'])){
23 | foreach ($config['ignored_annitation'] as $item) {
24 | AnnotationReader::addGlobalIgnoredName($item);
25 | }
26 | }
27 | }
28 | /**
29 | * 解析非@注解的文本注释
30 | * @param $refMethod
31 | * @param $isAll bool 是否获取全部,true则将带@开头的注释也包含
32 | * @return array|false
33 | */
34 | public static function parseTextAnnotation($refMethod,$isAll=false): array
35 | {
36 | $annotation = $refMethod->getDocComment();
37 | if (empty($annotation)) {
38 | return [];
39 | }
40 | if (preg_match('#^/\*\*(.*)\*/#s', $annotation, $comment) === false)
41 | return [];
42 | $comment = trim($comment [1]);
43 | if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false)
44 | return [];
45 | $data = [];
46 | foreach ($lines[1] as $line) {
47 | $line = trim($line);
48 | if (!empty ($line) && ($isAll===true || ($isAll===false && strpos($line, '@') !== 0))) {
49 | $data[] = $line;
50 | }
51 | }
52 | return $data;
53 | }
54 |
55 | /**
56 | * 根据路径获取类名
57 | * @param $path
58 | * @return string
59 | */
60 | protected function getClassName($path){
61 | $NameArr = explode("\\", $path);
62 | $name = lcfirst($NameArr[count($NameArr) - 1]);
63 | return $name;
64 | }
65 |
66 | /**
67 | * 获取并处理注解参数
68 | * @param $attrList
69 | * @return array
70 | */
71 | protected function getParameters($attrList){
72 | $attrs = [];
73 | foreach ($attrList as $item) {
74 | $value = "";
75 | if ($item instanceof ReflectionAttribute) {
76 | $attributeName = $item->getName();
77 | if (strpos($attributeName, 'apidoc') === false){
78 | continue;
79 | }
80 | $name = $this->getClassName($attributeName);
81 | $params = $item->getArguments();
82 | if (!empty($params)){
83 | if (is_array($params) && !empty($params[0]) && is_string($params[0]) && count($params)===1){
84 | $value = $params[0];
85 | }else{
86 | if (isset($params[0])){
87 | $paramObj = [];
88 | foreach ($params as $k=>$value) {
89 | $key = $k===0?'name':$k;
90 | $paramObj[$key]=$value;
91 | }
92 | }else{
93 | $paramObj = $params;
94 | }
95 | $value = $paramObj;
96 | }
97 | }
98 | }else{
99 | $name = $this->getClassName(get_class($item));
100 | $valueObj = Helper::objectToArray($item);
101 | if (array_key_exists('name',$valueObj) && count($valueObj)===1){
102 | $value = $valueObj['name']===null?true: $valueObj['name'];
103 | }else{
104 | $value = $valueObj;
105 | }
106 | }
107 | if (!empty($attrs[$name]) && is_array($attrs[$name]) && Helper::arrayKeyFirst($attrs[$name])===0){
108 | $attrs[$name][]=$value;
109 | }else if(!empty($attrs[$name])){
110 | $attrs[$name] = [$attrs[$name],$value];
111 | }else{
112 | $attrs[$name]=$value;
113 | }
114 | }
115 | return $attrs;
116 | }
117 |
118 | /**
119 | * 获取类的注解参数
120 | * @param ReflectionMethod $refMethod
121 | * @return array
122 | */
123 | public function getClassAnnotation($refClass){
124 | if (method_exists($refClass,'getAttributes')){
125 | $attributes = $refClass->getAttributes();
126 | }else{
127 | $attributes = [];
128 | }
129 | $readerAttributes = $this->parser->getClassAnnotations($refClass);
130 | return $this->getParameters(array_merge($attributes,$readerAttributes));
131 | }
132 |
133 | /**
134 | * 获取方法的注解参数
135 | * @param ReflectionMethod $refMethod
136 | * @return array
137 | */
138 | public function getMethodAnnotation(ReflectionMethod $refMethod){
139 | if (method_exists($refMethod,'getAttributes')){
140 | $attributes = $refMethod->getAttributes();
141 | }else{
142 | $attributes = [];
143 | }
144 | $readerAttributes = $this->parser->getMethodAnnotations($refMethod);
145 | return $this->getParameters(array_merge($attributes,$readerAttributes));
146 | }
147 |
148 | /**
149 | * 获取属性的注解参数
150 | * @param $property
151 | * @return array
152 | */
153 | public function getPropertyAnnotation($property){
154 | if (method_exists($property,'getAttributes')){
155 | $attributes = $property->getAttributes();
156 | }else{
157 | $attributes = [];
158 | }
159 | $readerAttributes = $this->parser->getPropertyAnnotations($property);
160 | return $this->getParameters(array_merge($attributes,$readerAttributes));
161 | }
162 |
163 | /**
164 | * 解析类的属性文本注释的var
165 | * @param $propertyTextAnnotations
166 | * @return array
167 | */
168 | protected static function parsesPropertyTextAnnotation($propertyTextAnnotations){
169 | $varLine = "";
170 | foreach ($propertyTextAnnotations as $item) {
171 | if (strpos($item, '@var') !== false){
172 | $varLine = $item;
173 | break;
174 | }
175 | }
176 | $type = "";
177 | $desc = "";
178 | if ($varLine){
179 | $varLineArr = preg_split('/\\s+/', $varLine);
180 | $type = !empty($varLineArr[1])?$varLineArr[1]:"";
181 | $desc = !empty($varLineArr[2])?$varLineArr[2]:"";
182 | }
183 | if (empty($desc) && strpos($propertyTextAnnotations[0], '@var') === false){
184 | $desc = $propertyTextAnnotations[0];
185 | }
186 | return [
187 | 'type'=>$type,
188 | 'desc'=>$desc,
189 | ];
190 | }
191 |
192 | /**
193 | * 获取类的属性参数
194 | * @param $classReflect
195 | * @return array
196 | */
197 | public function getClassPropertiesy($classReflect){
198 | $publicProperties = $classReflect->getProperties(\ReflectionProperty::IS_PUBLIC);
199 | $arr=[];
200 | foreach ($publicProperties as $property) {
201 | $propertyAnnotations = $this->getPropertyAnnotation($property);
202 | $item = [];
203 | if (!empty($propertyAnnotations['property'])){
204 | // 有apidoc注解
205 | $arr[] = $propertyAnnotations['property'];
206 | continue;
207 | }
208 | $propertyTextAnnotations = self::parseTextAnnotation($property,true);
209 | if (empty($propertyTextAnnotations)){
210 | // 无注释
211 | continue;
212 | }
213 | $textAnnotationsParams=static::parsesPropertyTextAnnotation($propertyTextAnnotations);
214 | $textAnnotationsParams['name'] =$property->getName();
215 | $arr[]=$textAnnotationsParams;
216 | }
217 | return $arr;
218 | }
219 |
220 |
221 |
222 | }
--------------------------------------------------------------------------------
/src/utils/DirAndFile.php:
--------------------------------------------------------------------------------
1 | $value,
20 | 'path'=>$sub_path,
21 | ];
22 | $children = static::getDirTree($sub_path);
23 | if (count($children)){
24 | $item['children'] = $children;
25 | }
26 | $arr[] = $item;
27 | }
28 | }
29 | }
30 | return $arr;
31 | }
32 |
33 | public static function getClassList($dir){
34 | if ($handle = opendir($dir)) {
35 | $file_list=[];
36 | while (false !== ($file = readdir($handle))) {
37 | if($file=='..' || $file=='.') continue;
38 | $filePath = static::formatPath($dir.'/'.$file,"/");
39 | if(is_file($filePath)) {
40 | if ('php' !== pathinfo($filePath, \PATHINFO_EXTENSION)) {
41 | continue;
42 | }
43 | $classes = self::findClasses($filePath);
44 | if (!empty($classes) && count($classes)){
45 | $file_list[] = [
46 | 'name'=>$classes[0],
47 | 'path'=>$filePath
48 | ];
49 | }else{
50 | $file_list=[];
51 | }
52 | continue;
53 | }
54 | $file_list[$file] = static::getClassList($filePath);
55 | foreach($file_list[$file] as $infile) {
56 | $file_list[] = $infile;
57 | }
58 | unset($file_list[$file]);
59 | }
60 | closedir($handle);
61 | return $file_list;
62 | }
63 | return [];
64 | }
65 | public static function getFileList($path){
66 | if(is_dir($path)) {
67 | $dirList = scandir($path);
68 | $list = [];
69 | foreach ($dirList as $dir) {
70 | if ($dir == '.' || $dir == '..') {
71 | continue;
72 | }
73 | $sub_path = DirAndFile::formatPath($path . '/' . $dir, "/");
74 | if (is_file($sub_path)){
75 | $list[]=[
76 | 'name'=>$dir,
77 | 'path'=>$sub_path
78 | ];;
79 | }
80 | }
81 | return $list;
82 | }
83 | return [];
84 | }
85 |
86 | public static function formatPath($path,$type="/"){
87 | if ($type==="/"){
88 | $path = str_replace("\\","/",$path);
89 | }else{
90 | $path = str_replace("/","\\",$path);
91 | $path = str_replace("\\\\","\\",$path);
92 | $endStr = substr($path, -1);
93 | if ($endStr=='\\'){
94 | $path = substr($path,0,strlen($path)-1);
95 | }
96 | }
97 | return $path;
98 | }
99 |
100 | private static function findClasses($path)
101 | {
102 | $contents = file_get_contents($path);
103 | $tokens = token_get_all($contents);
104 |
105 | $nsTokens = [\T_STRING => true, \T_NS_SEPARATOR => true];
106 | if (\defined('T_NAME_QUALIFIED')) {
107 | $nsTokens[T_NAME_QUALIFIED] = true;
108 | }
109 |
110 | $classes = [];
111 |
112 | $namespace = '';
113 | for ($i = 0; isset($tokens[$i]); ++$i) {
114 | $token = $tokens[$i];
115 |
116 | if (!isset($token[1])) {
117 | continue;
118 | }
119 |
120 | $class = '';
121 |
122 | switch ($token[0]) {
123 | case \T_NAMESPACE:
124 | $namespace = '';
125 | // If there is a namespace, extract it
126 | while (isset($tokens[++$i][1])) {
127 | if (isset($nsTokens[$tokens[$i][0]])) {
128 | $namespace .= $tokens[$i][1];
129 | }
130 | }
131 | $namespace .= '\\';
132 | break;
133 | case \T_CLASS:
134 | case \T_INTERFACE:
135 | case \T_TRAIT:
136 | // Skip usage of ::class constant
137 | $isClassConstant = false;
138 | for ($j = $i - 1; $j > 0; --$j) {
139 | if (!isset($tokens[$j][1])) {
140 | break;
141 | }
142 |
143 | if (\T_DOUBLE_COLON === $tokens[$j][0]) {
144 | $isClassConstant = true;
145 | break;
146 | } elseif (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT])) {
147 | break;
148 | }
149 | }
150 |
151 | if ($isClassConstant) {
152 | break;
153 | }
154 |
155 | // Find the classname
156 | while (isset($tokens[++$i][1])) {
157 | $t = $tokens[$i];
158 | if (\T_STRING === $t[0]) {
159 | $class .= $t[1];
160 | } elseif ('' !== $class && \T_WHITESPACE === $t[0]) {
161 | break;
162 | }
163 | }
164 |
165 | $classes[] = ltrim($namespace.$class, '\\');
166 | break;
167 | default:
168 | break;
169 | }
170 | }
171 |
172 | return $classes;
173 | }
174 |
175 |
176 | /**
177 | * 读取文件内容
178 | * @param $fileName
179 | * @return false|string
180 | */
181 | public static function getFileContent(string $fileName): string
182 | {
183 | $content = "";
184 | if (file_exists($fileName)) {
185 | $handle = fopen($fileName, "r");
186 | $content = fread($handle, filesize($fileName));
187 | fclose($handle);
188 | }
189 | return $content;
190 | }
191 |
192 | /**
193 | * 保存文件
194 | * @param $path
195 | * @param $str_tmp
196 | * @return bool
197 | */
198 | public static function createFile(string $path, string $str_tmp): bool
199 | {
200 | $pathArr = explode("/", $path);
201 | unset($pathArr[count($pathArr) - 1]);
202 | $dir = implode("/", $pathArr);
203 | if (!file_exists($dir)) {
204 | mkdir($dir, 0775, true);
205 | }
206 | $fp = fopen($path, "w") or die("Unable to open file!");
207 | fwrite($fp, $str_tmp); //存入内容
208 | fclose($fp);
209 | return true;
210 | }
211 |
212 | /**
213 | * 判断文件是否存在后,删除
214 | * @access private
215 | * @param string $path
216 | * @return bool
217 | */
218 | public static function unlink(string $path): bool
219 | {
220 | try {
221 | return is_file($path) && unlink($path);
222 | } catch (\Exception $e) {
223 | return false;
224 | }
225 | }
226 |
227 | public static function checkFileExist(string $path)
228 | {
229 | try {
230 | return $path;
231 | } catch (\Exception $e) {
232 | return $e;
233 | }
234 | }
235 |
236 | public static function deleteDir($path) {
237 | if (!is_dir($path)) {
238 | return false;
239 | }
240 | $open = opendir($path);
241 | if (!$open) {
242 | return false;
243 | }
244 | while (($v = readdir($open)) !== false) {
245 | if ('.' == $v || '..' == $v) {
246 | continue;
247 | }
248 | $item = $path . '/' . $v;
249 | if (is_file($item)) {
250 | unlink($item);
251 | continue;
252 | }
253 | static::deleteDir($item);
254 | }
255 | closedir($open);
256 | return rmdir($path);
257 | }
258 | }
--------------------------------------------------------------------------------
/src/parses/ParseApiMenus.php:
--------------------------------------------------------------------------------
1 | config = $config;
34 | }
35 |
36 | /**
37 | * 生成api接口数据
38 | * @param string $appKey
39 | * @param bool $isParseDetail 是否解析接口明细
40 | * @return array
41 | */
42 | public function renderApiMenus(string $appKey,bool $isParseDetail=false): array
43 | {
44 | $currentAppConfig = Helper::getCurrentAppConfig($appKey);
45 | $currentApp = $currentAppConfig['appConfig'];
46 | $this->currentApp = $currentApp;
47 | $this->appKey = $appKey;
48 |
49 | $controllers = [];
50 | if (!empty($currentApp['controllers']) && count($currentApp['controllers']) > 0) {
51 | // 配置的控制器列表
52 | $controllers = $this->getConfigControllers($currentApp['path'],$currentApp['controllers']);
53 | }else if(!empty($currentApp['path']) && is_array($currentApp['path']) && count($currentApp['path'])){
54 | // 读取paths的
55 | foreach ($currentApp['path'] as $path) {
56 | $controllersList = $this->getDirControllers($path);
57 | $controllers = array_merge($controllers,$controllersList);
58 | }
59 | } else if(!empty($currentApp['path']) && is_string($currentApp['path'])){
60 | // 默认读取path下所有的
61 | $controllers = $this->getDirControllers($currentApp['path']);
62 | }
63 | $apiData = [];
64 | if (!empty($controllers) && count($controllers) > 0) {
65 | foreach ($controllers as $class) {
66 | $classData = $this->parseController($class,$isParseDetail);
67 | if ($classData !== false) {
68 | $apiData[] = $classData;
69 | }
70 | }
71 | }
72 | // 排序
73 | $apiList = Helper::arraySortByKey($apiData);
74 |
75 | // 接口分组
76 | if (!empty($currentApp['groups'])){
77 | $apiList = ParseApiMenus::mergeApiGroup($apiList,$currentApp['groups']);
78 | }
79 |
80 | $json = array(
81 | "data" => $apiList,
82 | "tags" => $this->tags,
83 | "groups" => $this->groups,
84 | );
85 | return $json;
86 | }
87 |
88 | /**
89 | * 获取生成文档的控制器列表
90 | * @param string $path
91 | * @return array
92 | */
93 | public function getConfigControllers(string $path,$appControllers): array
94 | {
95 | $controllers = [];
96 | if (!empty($appControllers) && count($appControllers) > 0) {
97 | foreach ($appControllers as $item) {
98 | $classPath = $path."\\".$item;
99 | if ( class_exists($classPath)) {
100 | $controllers[] = $classPath;
101 | }
102 | }
103 | }
104 | return $controllers;
105 | }
106 |
107 | /**
108 | * 获取目录下的控制器列表
109 | * @param string $path
110 | * @return array
111 | */
112 | public function getDirControllers(string $path): array
113 | {
114 |
115 | if ($path) {
116 | if (strpos(APIDOC_ROOT_PATH, '/') !== false) {
117 | $pathStr = str_replace("\\", "/", $path);
118 | } else {
119 | $pathStr = $path;
120 | }
121 | $dir = APIDOC_ROOT_PATH . $pathStr;
122 | } else {
123 | $dir = APIDOC_ROOT_PATH . $this->controller_layer;
124 | }
125 | $controllers = [];
126 | if (is_dir($dir)) {
127 | $controllers = $this->scanDir($dir, $path);
128 | }
129 | return $controllers;
130 | }
131 |
132 |
133 |
134 | protected function scanDir($dir) {
135 |
136 | $classList= DirAndFile::getClassList($dir);
137 | $list=[];
138 |
139 | $configFilterController = !empty($this->config['filter_controllers']) ? $this->config['filter_controllers'] : [];
140 | $currentAppFilterController = !empty($this->currentApp['filter_controllers']) ? $this->currentApp['filter_controllers'] : [];
141 | $filterControllers = array_merge($configFilterController,$currentAppFilterController);
142 |
143 | $configFilterDir = !empty($this->config['filter_dirs']) ? $this->config['filter_dirs'] : [];
144 | $currentAppFilterDir = !empty($this->currentApp['filter_dirs']) ? $this->currentApp['filter_dirs'] : [];
145 | $filterDirList = array_merge($configFilterDir,$currentAppFilterDir);
146 | $filterDirs=[];
147 | foreach ($filterDirList as $dirItem) {
148 | $dirItemPath = DirAndFile::formatPath($dirItem,"/");
149 | $filterDirs[]=$dirItemPath;
150 | }
151 |
152 | foreach ($classList as $item) {
153 | $classNamespace = $item['name'];
154 |
155 | $isFilterDir = false;
156 | foreach ($filterDirs as $dirItem) {
157 | if (strpos($item['path'], $dirItem) !== false){
158 | $isFilterDir=true;
159 | }
160 | }
161 | if ($isFilterDir){
162 | continue;
163 | }else if (
164 | !in_array($classNamespace, $filterControllers) &&
165 | $this->config['definitions'] != $classNamespace
166 |
167 | ) {
168 | $list[] = $classNamespace;
169 | }
170 | }
171 |
172 | return $list;
173 | }
174 |
175 | public function parseController($class,$isParseDetail=false)
176 | {
177 |
178 |
179 | $refClass = new ReflectionClass($class);
180 | $classTextAnnotations = ParseAnnotation::parseTextAnnotation($refClass);
181 | $data = (new ParseAnnotation($this->config))->getClassAnnotation($refClass);
182 | if (in_array("NotParse", $classTextAnnotations) || isset($data['notParse'])) {
183 | return false;
184 | }
185 | $controllersName = $refClass->getShortName();
186 | $data['controller'] = $controllersName;
187 | $data['path'] = $class;
188 | $data['appKey'] = $this->appKey;
189 | if (!empty($data['group']) && !in_array($data['group'], $this->groups)) {
190 | $this->groups[] = $data['group'];
191 | }
192 |
193 | if (empty($data['title'])) {
194 | if (!empty($classTextAnnotations) && count($classTextAnnotations) > 0) {
195 | $data['title'] = $classTextAnnotations[0];
196 | } else {
197 | $data['title'] = $controllersName;
198 | }
199 | }
200 | $data['title'] = Lang::getLang($data['title']);
201 | $methodList = [];
202 | $data['menuKey'] = Helper::createRandKey($data['controller']);
203 | $isNotDebug = in_array("NotDebug", $classTextAnnotations) || isset($data['notDebug']);
204 | $parseApiDetail = new ParseApiDetail($this->config);
205 | foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
206 | if ($isParseDetail){
207 | $methodItem = $parseApiDetail->parseApiMethod($refClass,$refMethod,$this->currentApp);
208 | }else{
209 | $methodItem = $this->parseApiMethod($refClass,$refMethod);
210 | }
211 | if ($methodItem===false){
212 | continue;
213 | }
214 | if ($isNotDebug) {
215 | $methodItem['notDebug'] = true;
216 | }
217 | $methodList[] = $methodItem;
218 | }
219 | $data['children'] = $methodList;
220 | if (count($methodList)===0){
221 | return false;
222 | }
223 | return $data;
224 | }
225 |
226 |
227 | protected function parseApiMethod($refClass,$refMethod){
228 | $config = $this->config;
229 | if (empty($refMethod->name)) {
230 | return false;
231 | }
232 | if(!empty($config['ignored_methods']) && in_array($refMethod->name, $config['ignored_methods'])){
233 | return false;
234 | }
235 |
236 | try {
237 | $textAnnotations = ParseAnnotation::parseTextAnnotation($refMethod);
238 |
239 | $methodAnnotation = (new ParseAnnotation($this->config))->getMethodAnnotation($refMethod);
240 | // 标注不解析的方法
241 | if (in_array("NotParse", $textAnnotations) || isset($methodAnnotation['notParse']) || empty($methodAnnotation)) {
242 | return false;
243 | }
244 | $methodInfo = ParseApiDetail::handleApiBaseInfo($methodAnnotation,$refClass->name,$refMethod->name,$textAnnotations,$config);
245 | $methodInfo['appKey'] = !empty($this->currentApp['appKey'])?$this->currentApp['appKey']:"";
246 | return Helper::getArrayValuesByKeys($methodInfo,['title','method','url','author','tag','name','menuKey','appKey']);
247 | }catch (AnnotationException $e) {
248 | throw new ErrorException($e->getMessage());
249 | }
250 |
251 | }
252 |
253 |
254 |
255 | /**
256 | * 对象分组到tree
257 | * @param $tree
258 | * @param $objectData
259 | * @param string $childrenField
260 | * @return array
261 | */
262 | public static function objtctGroupByTree($tree,$objectData,$childrenField='children'){
263 | $data = [];
264 | foreach ($tree as $node){
265 | if (!empty($node[$childrenField])){
266 | $node[$childrenField] = static::objtctGroupByTree($node[$childrenField],$objectData);
267 | }else if (!empty($objectData[$node['name']])){
268 | $node[$childrenField] = $objectData[$node['name']];
269 | }
270 | $node['menuKey'] = Helper::createRandKey( $node['name']);
271 | $data[] = $node;
272 | }
273 | return $data;
274 | }
275 |
276 | protected static function getAppGroupNames($groups){
277 | $groupNames = [];
278 | foreach ($groups as $item) {
279 | if (!empty($item['name'])){
280 | $groupNames[]=$item['name'];
281 | }
282 | if (!empty($item['children']) && count($item['children'])){
283 | $childrenNames = self::getAppGroupNames($item['children']);
284 | foreach ($childrenNames as $childrenName) {
285 | $groupNames[]=$childrenName;
286 | }
287 | }
288 | }
289 | return $groupNames;
290 | }
291 |
292 | /**
293 | * 合并接口到应用分组
294 | * @param $apiData
295 | * @param $groups
296 | * @return array
297 | */
298 | public static function mergeApiGroup($apiData,$groups){
299 | if (empty($groups) || count($apiData)<1){
300 | return $apiData;
301 | }
302 | $groupNames = static::getAppGroupNames($groups);
303 | $apiObject = [];
304 | foreach ($apiData as $controller){
305 | if (!empty($controller['group']) && in_array($controller['group'],$groupNames)){
306 | if (!empty($apiObject[$controller['group']])){
307 | $apiObject[$controller['group']][] = $controller;
308 | }else{
309 | $apiObject[$controller['group']] = [$controller];
310 | }
311 | }else{
312 | if (!empty($apiObject['notGroup'])){
313 | $apiObject['notGroup'][] = $controller;
314 | }else{
315 | $apiObject['notGroup'] = [$controller];
316 | }
317 | }
318 | }
319 | if (!empty($apiObject['notGroup'])){
320 | array_unshift($groups,['title'=>'未分组','name'=>'notGroup']);
321 | }
322 | $res = static::objtctGroupByTree($groups,$apiObject);
323 | return $res;
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/src/generator/ParseTemplate.php:
--------------------------------------------------------------------------------
1 | replaceForeach($tplContent,$params);
18 | $tplContent = $this->replaceParams($tplContent,$params);
19 | $tplContent = $this->replaceIf($tplContent,$params);
20 | $tplContent = preg_replace("/\s+\r\n/is", "\r\n", $tplContent);
21 | return $tplContent;
22 | }
23 |
24 | /**
25 | * 替换变量
26 | * @param $tplContent
27 | * @param $params
28 | * @return array|string|string[]|null
29 | */
30 | protected function replaceParams($tplContent,$params){
31 | $key = '{$%%}';
32 | $pattern = '#' . str_replace('%%', '(.+?)' , preg_quote($key, '#')) . '#';
33 | $tplContent = preg_replace_callback($pattern, function ($matches)use ($params){
34 | $k = $matches[1];
35 | if (strpos($k, '(') !== false){
36 | $tagArr = explode("(", $k);
37 | $fun = $tagArr[0];
38 | $k = str_replace(")", "",$tagArr[1] );
39 | $v = $this->getObjectValueByKeys($params,$k);
40 | if (empty($v)){
41 | return "";
42 | }
43 | if ($fun === "lower"){
44 | return Helper::lower($v);
45 | }else if ($fun === "snake"){
46 | return Helper::snake($v);
47 | }else if ($fun === "lcfirst"){
48 | return lcfirst($v);
49 | }else if ($fun === "ucfirst"){
50 | return ucfirst($v);
51 | }else if ($fun === "count"){
52 | return count($v);
53 | }
54 | }
55 | $value = $this->getObjectValueByKeys($params,$k);
56 | if (is_bool($value)){
57 | return $value==true?'true':'false';
58 | }else if (is_array($value)){
59 | return $k;
60 | }
61 | return $value;
62 | }, $tplContent);
63 | return $tplContent;
64 | }
65 |
66 | /**
67 | * 替换if内容
68 | * @param $tplContent
69 | * @param $params
70 | * @return array|mixed|string|string[]
71 | * @throws \Exception
72 | */
73 | protected function replaceIf($tplContent,$params){
74 | $res = [];
75 | $label = "if";
76 | $labelList = $this->parseLabel($tplContent,$label);
77 | if (!empty($labelList) && count($labelList)>0){
78 | foreach ($labelList as $item) {
79 | $itemStr =$item;
80 | $ifChildren= $this->parseLabel($itemStr,$label,"children");
81 |
82 | if (!empty($ifChildren) && count($ifChildren)>0){
83 | foreach ($ifChildren as $ifChild){
84 | $itemChildrenContent= $this->getIfContent($ifChild);
85 | $itemStr = str_replace($ifChild, $itemChildrenContent,$itemStr );
86 | }
87 | }
88 | $itemContent= $this->getIfContent($itemStr);
89 | $tplContent = str_replace($item, $itemContent,$tplContent );
90 | }
91 | }
92 |
93 | return $tplContent;
94 | }
95 | protected function parseForeach($str,$params){
96 | if (preg_match('#{foreach (.+?) as (.+?)=>(.+?)}#s', $str, $matches)){
97 | $complete = $matches[0];
98 | $condition = $matches[1];
99 | $keyField = str_replace("$", "",$matches[2] );
100 | $itemField = str_replace("$", "",$matches[3] );
101 | $conditionKey = str_replace("$", "",$condition );
102 | $forListData = $this->getObjectValueByKeys($params,$conditionKey);
103 | $contentStr = str_replace($complete, "",$str);
104 | $contentStr = substr($contentStr,0,-10);
105 | return [
106 | 'list'=>$forListData,
107 | 'keyField'=>$keyField,
108 | 'itemField'=>$itemField,
109 | 'content'=>$contentStr
110 | ];
111 | }
112 | return [];
113 | }
114 |
115 | /**
116 | * 获取所有foreach标签
117 | * @param $str
118 | * @return array
119 | * @throws \Exception
120 | */
121 | protected function getAllForeachLabel($str){
122 | $tree = [];
123 | $label = "foreach";
124 | $labelList = $this->parseLabel($str,$label);
125 | if (!empty($labelList) && count($labelList)>0){
126 | foreach ($labelList as $itemLabel) {
127 | $labelChildrenList = $this->parseLabel($itemLabel,$label,"children");
128 | if (!empty($labelChildrenList) && count($labelChildrenList)>0){
129 | $childrenList = [];
130 | foreach ($labelChildrenList as $item) {
131 | $childrenList[]=[
132 | 'str'=>$item,
133 | 'children' => []
134 | ];
135 | }
136 | $tree[]=[
137 | 'str'=>$itemLabel,
138 | 'children' => $childrenList
139 | ];
140 | }else{
141 | $tree[]=[
142 | 'str'=>$itemLabel,
143 | 'children' => []
144 | ];
145 | }
146 | }
147 | }
148 | return $tree;
149 | }
150 | // 解析foreach
151 | protected function replaceForeach($html,$params,$level=""){
152 | $allLabelData= $this->getAllForeachLabel($html);
153 | $res = [];
154 | if (count($allLabelData)>0){
155 | // 遍历每个foreach标签
156 | foreach ($allLabelData as $labelItem) {
157 | $itemStr = $labelItem['str'];
158 | $forOption = $this->parseForeach($labelItem['str'],$params);
159 | $itemContent="";
160 | if (!empty($forOption['list']) && count($forOption['list'])>0){
161 | // 处理每行数据
162 | foreach ($forOption['list'] as $rowKey=>$row) {
163 | $rowData = [$forOption['itemField']=>$row,$forOption['keyField']=>$rowKey];
164 | $rowParams = array_merge($params,$rowData);
165 | // 存在子标签,处理子标签
166 | if (!empty($labelItem['children']) && count($labelItem['children'])>0){
167 | $itemStrContent = "";
168 | foreach ($labelItem['children'] as $childLabel){
169 | $childContents = "";
170 | $childStr = $childLabel['str'];
171 | $childDataList = $this->parseForeach($childLabel['str'],$rowParams);
172 | // 处理子标签数据
173 | if (!empty($childDataList['list']) && count($childDataList['list'])>0){
174 | foreach ($childDataList['list'] as $childDataKey=>$childDataItem) {
175 | // 子标签每行数据
176 | $childDataItemData = [$childDataList['itemField']=>$childDataItem,$childDataList['keyField']=>$childDataKey,];
177 | $contentsStr= $this->getForContent($childDataList['content'],array_merge($rowParams,$childDataItemData));
178 | $contentsStr =ltrim($contentsStr,"\r\n");
179 | if (!empty(Helper::trimEmpty($contentsStr))){
180 | $childContents.= $contentsStr;
181 | }
182 | }
183 | }
184 | $itemStrContent.= str_replace($childLabel['str'], $childContents,$forOption['content']);
185 | }
186 | $rowContent=$this->replaceParams($itemStrContent,$rowParams);
187 | $itemContentStr=$this->replaceIf($rowContent,$rowParams);
188 | if (!empty(Helper::trimEmpty($itemContentStr))){
189 | $itemContent.= $itemContentStr;
190 | }
191 | }else{
192 | $rowContent=$this->getForContent($forOption['content'],$rowParams);
193 | if (empty(Helper::trimEmpty($rowContent))){
194 | $rowContent= "";
195 | }
196 | $itemContent.= $rowContent;
197 | }
198 | $itemContent =trim($itemContent,"\r\n");
199 | }
200 | }
201 | $html = str_replace($labelItem['str'], $itemContent,$html );
202 | }
203 | }
204 | return $html;
205 |
206 | }
207 |
208 | /**
209 | * 获取foreach内容
210 | * @param $str
211 | * @param $params
212 | * @return array|mixed|string|string[]
213 | * @throws \Exception
214 | */
215 | protected function getForContent($str,$params){
216 | $content = $str;
217 | if (!empty($params)){
218 | $content = $this->replaceParams($content,$params);
219 | $content = $this->replaceIf($content,$params);
220 | }
221 | return $content;
222 | }
223 |
224 |
225 | /**
226 | * 获取if条件的内容
227 | * @param $str
228 | * @return mixed|string
229 | */
230 | protected function getIfContent($str){
231 | if (preg_match('#{if (.+?)}(.*?){/if}#s', $str, $matches)){
232 | if (eval("return $matches[1];")){
233 | // 条件成立
234 | return $matches[2];
235 | }
236 | }
237 | return "";
238 | }
239 |
240 |
241 | /**
242 | * 解析指定标签
243 | * @param $str
244 | * @param $label
245 | * @param string $level
246 | * @return array
247 | * @throws \Exception
248 | */
249 | protected function parseLabel($str,$label,$level=""){
250 | // 后面的 flag 表示记录偏移量
251 | preg_match_all('!({/?'.$label.' ?}?)!', $str, $matches, PREG_OFFSET_CAPTURE);
252 | // 用数组来模拟栈
253 | $stack = [];
254 | $top = null;
255 | $result = [];
256 | foreach ($matches[0] as $k=>[$match, $offset]) {
257 | // 当取标签内容时,排除第一个和最后一个标签
258 | if ($level === 'children' && ($k==0 || $k>=count($matches[0])-1)){
259 | continue;
260 | }
261 | // 判断匹配到的如果是 开始标签
262 | if ($match === '{'.$label.' ') {
263 | $stack[] = $offset;
264 | // 记录开始的位置
265 | if ($top === null) {
266 | $top = $offset;
267 | }
268 | // 如果不是
269 | } else {
270 | // 从栈底部拿一个出来
271 | $pop = array_pop($stack);
272 | // 如果取出来的是 null 就说明存在多余的 标签
273 | if ($pop === null) {
274 | throw new \Exception('语法错误,存在多余的 {/'.$label.'} 标签');
275 | }
276 | // 如果取完后栈空了
277 | if (empty($stack)) {
278 | // offset 是匹配到的开始标签(前面)位置,加上内容的长度
279 | $newOffset = $offset + strlen($match)-$top;
280 | // 从顶部到当前的偏移就是这个标签里的内容
281 | $result[] = substr($str, $top, $newOffset);
282 | // 重置 top 开始下一轮
283 | $top = null;
284 | }
285 | }
286 | }
287 | // 如果运行完了,栈里面还有东西,那就说明缺少闭合标签。
288 | if (!empty($stack)) {
289 | throw new \Exception('语法错误,存在未闭合的 {/'.$label.'} 标签');
290 | }
291 | return $result;
292 | }
293 |
294 | /**
295 | * 根据keys获取对象中的值
296 | * @param $array
297 | * @param $keyStr
298 | * @param string $delimiter
299 | * @return mixed|null
300 | */
301 | public function getObjectValueByKeys($array, $keyStr, $delimiter = '.')
302 | {
303 | $keys = explode($delimiter, $keyStr);
304 | if (preg_match_all('#\[(.+?)]#s', $keyStr, $matches)){
305 | $value = $array;
306 | if (!empty($matches[1])){
307 | $matchesIndex=0;
308 | foreach ($keys as $keyItem) {
309 | if (strpos($keyItem, '[') !== false) {
310 | $tagArr = explode("[", $keyItem);
311 | if (!empty($value[$tagArr[0]]) && !empty($value[$tagArr[0]][$matches[1][$matchesIndex]])){
312 | $value =$value[$tagArr[0]][$matches[1][$matchesIndex]];
313 | }else{
314 | $value =null;
315 | break;
316 | }
317 | $matchesIndex=$matchesIndex+1;
318 | }else{
319 | $value =$value[$keyItem];
320 | }
321 | }
322 | }
323 | return $value;
324 | }else if (sizeof($keys) > 1) {
325 | $value = $array;
326 | foreach ($keys as $key){
327 | if (!empty($value[$key])){
328 | $value = $value[$key];
329 | }else{
330 | $value =null;
331 | break;
332 | }
333 | }
334 | return $value;
335 | } else {
336 | return $array[$keyStr] ?? null;
337 | }
338 | }
339 |
340 |
341 | }
--------------------------------------------------------------------------------
/src/generator/Index.php:
--------------------------------------------------------------------------------
1 | '',
18 | // 数据库编码,默认为utf8
19 | 'charset' => 'utf8',
20 | // 数据库引擎,默认为 InnoDB
21 | 'engine' => 'InnoDB',
22 | ];
23 |
24 | protected $systemDefaultValues = [
25 | 'CURRENT_TIMESTAMP'
26 | ];
27 |
28 | public function __construct($config)
29 | {
30 | $this->config = $config;
31 | if (!empty($config['database'])){
32 | if (!empty($config['database']['prefix'])){
33 | $this->databaseConfig['prefix'] = $config['database']['prefix'];
34 | }
35 | if (!empty($config['database']['charset'])){
36 | $this->databaseConfig['charset'] = $config['database']['charset'];
37 | }
38 | if (!empty($config['database']['engine'])){
39 | $this->databaseConfig['engine'] = $config['database']['engine'];
40 | }
41 | }
42 | }
43 |
44 | public function create($params){
45 | $appKey = $params['form']['appKey'];
46 | $currentAppConfig = Helper::getCurrentAppConfig($appKey);
47 | $currentApps = $currentAppConfig['apps'];
48 | $currentApp = $currentAppConfig['appConfig'];
49 | $generatorItem = $this->config['generator'][$params['index']];
50 |
51 | $checkParams = $this->checkFilesAndHandleParams($generatorItem,$params,$currentApps);
52 | $tplParams = $checkParams['tplParams'];
53 | // 注册中间件并执行before
54 | if (!empty($generatorItem['middleware']) && count($generatorItem['middleware'])){
55 | foreach ($generatorItem['middleware'] as $middleware) {
56 | $instance = new $middleware;
57 | $this->middlewares[] = $instance;
58 | if (method_exists($instance, 'before')) {
59 | $middlewareRes = $instance->before($tplParams);
60 | if (!empty($middlewareRes)){
61 | $tplParams = $middlewareRes;
62 | }
63 | }
64 | }
65 | }
66 |
67 | $this->createModels($checkParams['createModels'],$tplParams);
68 | $this->createFiles($checkParams['createFiles'],$tplParams);
69 | //执行after
70 | if (count($this->middlewares)){
71 | foreach ($this->middlewares as $middleware) {
72 | if (method_exists($instance, 'after')) {
73 | $instance->after($tplParams);
74 | }
75 | }
76 | }
77 | return $tplParams;
78 | }
79 |
80 | /**
81 | * 验证文件及处理模板数据
82 | * @param $generatorItem
83 | * @param $params
84 | * @param $currentApps
85 | * @return array
86 | */
87 | protected function checkFilesAndHandleParams($generatorItem,$params,$currentApps){
88 | // 组成模板参数
89 | $tplParams=[
90 | 'form'=>$params['form'],
91 | 'tables'=>$params['tables'],
92 | 'app'=>$currentApps
93 | ];
94 | $createFiles = [];
95 | if (!empty($params['files']) && count($params['files'])>0) {
96 | $files = $params['files'];
97 | foreach ($files as $file) {
98 | $fileConfig = Helper::getArrayFind($generatorItem['files'], function ($item) use ($file) {
99 | if ($file['name'] === $item['name']) {
100 | return true;
101 | }
102 | return false;
103 | });
104 |
105 | $filePath = $file['path'];
106 | if (!empty($fileConfig['namespace'])) {
107 | $fileNamespace = Helper::replaceCurrentAppTemplate($fileConfig['namespace'], $currentApps);
108 | $fileNamespace = Helper::replaceTemplate($fileNamespace, $params['form'],"form.");
109 | } else {
110 | $fileNamespace = $filePath;
111 | }
112 | $fileNamespaceEndStr = substr($fileNamespace, -1);
113 | if ($fileNamespaceEndStr == '\\') {
114 | $fileNamespace = substr($fileNamespace, 0, strlen($fileNamespace) - 1);
115 | }
116 | $template = Helper::replaceCurrentAppTemplate($fileConfig['template'], $currentApps);
117 | $template = Helper::replaceTemplate($template, $params['form'],"form.");
118 | $tplParams[$file['name']] = [
119 | 'class_name' => $file['value'],
120 | 'path' => $filePath,
121 | 'namespace' => $fileNamespace,
122 | 'template' => $template
123 | ];
124 |
125 | // 验证模板是否存在
126 | $templatePath =DirAndFile::formatPath( APIDOC_ROOT_PATH . $template,"/");
127 | if (is_readable($templatePath) == false) {
128 | throw new ErrorException("template not found", [
129 | 'template' => $template
130 | ]);
131 | }
132 | // 验证是否已存在生成的文件
133 | $fileFullPath = DirAndFile::formatPath(APIDOC_ROOT_PATH . $filePath, "/");
134 | $type = "folder";
135 | if (strpos($fileFullPath, '.php') !== false) {
136 | // 路径为php文件,则验证文件是否存在
137 | if (is_readable($fileFullPath) == false) {
138 | throw new ErrorException("file not exists", [
139 | 'filepath' => $filePath
140 | ]);
141 | }
142 | $type = "file";
143 | } else {
144 | $fileName = !empty($file['value']) ? $file['value'] : "";
145 | $fileFullPath = $fileFullPath . "/" . $fileName . ".php";
146 | if (is_readable($fileFullPath)) {
147 | throw new ErrorException("file already exists",[
148 | 'filepath' => DirAndFile::formatPath($filePath) . $fileName . ".php"
149 | ]);
150 | }
151 | }
152 | $createFiles[] = [
153 | 'fileFullPath' => $fileFullPath,
154 | 'template' => $template,
155 | 'templatePath'=>$templatePath,
156 | 'type' => $type
157 | ];
158 | }
159 | }
160 |
161 | $createModels = $this->checkModels($generatorItem,$tplParams,$currentApps,$params);
162 | $tplParams['tables'] = $createModels['tables'];
163 | return [
164 | 'tplParams'=>$tplParams,
165 | 'createFiles'=>$createFiles,
166 | 'createModels' =>$createModels['createModels']
167 | ];
168 | }
169 |
170 | /**
171 | * 验证模型及表
172 | * @param $generatorItem
173 | * @param $tplParams
174 | * @return array
175 | */
176 | protected function checkModels($generatorItem,$tplParams,$currentApps,$params=[]){
177 | if (empty($this->config['database_query_function'])){
178 | throw new ErrorException("not datatable_query_function config");
179 | }
180 |
181 | $res="";
182 | $tabls = $tplParams['tables'];
183 | $newTables = [];
184 | $createModels = [];
185 | if (!empty($tabls) && count($tabls)){
186 | foreach ($tabls as $k=>$table) {
187 | $tableConfig = $generatorItem['table'];
188 | $fileFullPath="";
189 | if (!empty($table['model_name'])){
190 | $namespace = Helper::replaceCurrentAppTemplate($tableConfig['items'][$k]['namespace'], $currentApps);
191 | $namespace = Helper::replaceTemplate($namespace, $params['form'],"form.");
192 | $path = Helper::replaceCurrentAppTemplate($tableConfig['items'][$k]['path'], $currentApps);
193 | $path = Helper::replaceTemplate($path, $params['form'],"form.");
194 | $template = $tableConfig['items'][$k]['template'];
195 |
196 | // 验证模板是否存在
197 | $templatePath = DirAndFile::formatPath(APIDOC_ROOT_PATH . $template,"/");
198 | if (is_readable($templatePath) == false) {
199 | throw new ErrorException("template not found", [
200 | 'template' => $template
201 | ]);
202 | }
203 | $tplParams['tables'][$k]['class_name'] =$table['model_name'];
204 | // 验证模型是否已存在
205 | $fileName = $table['model_name'];
206 | $fileFullPath = DirAndFile::formatPath(APIDOC_ROOT_PATH.$path). "/" . $fileName . ".php";
207 | if (is_readable($fileFullPath)) {
208 | throw new ErrorException("file already exists", [
209 | 'filepath' => DirAndFile::formatPath($path) . "/" . $fileName . ".php"
210 | ]);
211 | }
212 | }
213 | // 验证表是否存在
214 | if ($table['table_name']){
215 | $table_name = $this->databaseConfig['prefix'].$table['table_name'];
216 | $isTable = $this->config['database_query_function']('SHOW TABLES LIKE '."'".$table_name."'");
217 | if ($isTable){
218 | throw new ErrorException("datatable already exists", [
219 | 'table' => $table_name
220 | ]);
221 | }
222 | }
223 | $table['namespace']=$namespace;
224 | $table['path']=$path;
225 | $table['model_path']=$path;
226 | $newTables[]=$table;
227 | $createModels[]=[
228 | 'namespace'=>$namespace,
229 | 'template'=>$template,
230 | 'path'=>$path,
231 | 'templatePath' =>$templatePath,
232 | 'table'=>$table,
233 | 'fileFullPath'=>$fileFullPath
234 | ];
235 | }
236 | }
237 | return ['createModels'=>$createModels,'tables'=>$newTables];
238 |
239 | }
240 |
241 | /**
242 | * 创建文件
243 | * @param $createFiles
244 | * @param $tplParams
245 | * @return mixed
246 | */
247 | protected function createFiles($createFiles,$tplParams){
248 |
249 | if (!empty($createFiles) && count($createFiles)>0){
250 | foreach ($createFiles as $fileItem) {
251 | $html = (new ParseTemplate())->compile($fileItem['templatePath'],$tplParams);
252 | if ($fileItem['type'] === "file"){
253 | // 路径为文件,则添加到该文件
254 | $pathFileContent = DirAndFile::getFileContent($fileItem['fileFullPath']);
255 | $content = $pathFileContent."\r\n".$html;
256 | DirAndFile::createFile($fileItem['fileFullPath'],$content);
257 | }else{
258 | DirAndFile::createFile($fileItem['fileFullPath'],$html);
259 | }
260 | }
261 | }
262 | return $tplParams;
263 | }
264 |
265 | /**
266 | * 创建模型文件
267 | * @param $createModels
268 | * @param $tplParams
269 | */
270 | protected function createModels($createModels,$tplParams){
271 | if (!empty($createModels) && count($createModels)>0){
272 | foreach ($createModels as $k=>$item) {
273 | $table = $item['table'];
274 | if (!empty($table['table_name'])){
275 | $res = $this->createTable($table);
276 | }
277 | if (!empty($table['model_name'])){
278 | $tplParams['tables'][$k]['class_name'] =$table['model_name'];
279 | $html = (new ParseTemplate())->compile($item['templatePath'],$tplParams);
280 | DirAndFile::createFile($item['fileFullPath'],$html);
281 | }
282 |
283 | }
284 | }
285 | }
286 |
287 | /**
288 | * 创建数据表
289 | * @return mixed
290 | */
291 | protected function createTable($table){
292 | $datas = $table['datas'];
293 | $comment= "";
294 | if (!empty($table['table_comment'])){
295 | $comment =$table['table_comment'];
296 | }
297 | $table_name = $this->databaseConfig['prefix'].$table['table_name'];
298 | $table_data = '';
299 | $main_keys = '';
300 | $defaultNullTypes = ['timestamp'];
301 | foreach ($datas as $k=>$item){
302 | if (!empty($item['not_table_field'])){
303 | continue;
304 | }
305 | $table_field="`".$item['field']."` ".$item['type'];
306 | if (!empty($item['length'])){
307 | $table_field.="(".$item['length'].")";
308 | }
309 |
310 | if (!empty($item['main_key'])){
311 | $main_keys.=$item['field'];
312 | $table_field.=" NOT NULL";
313 | }else if (!empty($item['not_null'])){
314 | $table_field.=" NOT NULL";
315 | }
316 | if (!empty($item['incremental']) && !empty($item['main_key'])){
317 | $table_field.=" AUTO_INCREMENT";
318 | }
319 | if (!empty($item['default']) || (isset($item['default']) && $item['default']=="0")){
320 | $defaultValue = "'".$item['default']."'";
321 | if (in_array($item['default'],$this->systemDefaultValues)){
322 | $defaultValue = $item['default'];
323 | }
324 | $table_field.=" DEFAULT ".$defaultValue."";
325 | }else if (!empty($item['main_key']) && !$item['not_null']){
326 | $table_field.=" DEFAULT NULL";
327 | }else if (in_array($item['type'],$defaultNullTypes) && empty($item['not_null'])){
328 | $table_field.=" NULL DEFAULT NULL";
329 | }
330 | $fh = $k < (count($datas)-1)?",":"";
331 | $table_field.=" COMMENT '".$item['desc']."'".$fh;
332 | $table_data.=$table_field;
333 | }
334 | $primaryKey = "";
335 | if (!empty($main_keys)){
336 | $table_data.=",";
337 | $primaryKey = "PRIMARY KEY (`$main_keys`)";
338 | }
339 |
340 | $charset = $this->databaseConfig['charset'];
341 | $engine = $this->databaseConfig['engine'];
342 | $sql = "CREATE TABLE IF NOT EXISTS `$table_name` (
343 | $table_data
344 | $primaryKey
345 | ) ENGINE=$engine DEFAULT CHARSET=$charset COMMENT='$comment' AUTO_INCREMENT=1 ;";
346 |
347 | try {
348 | $this->config['database_query_function']($sql);
349 | return true;
350 | } catch (\Exception $e) {
351 | throw new ErrorException("datatable create error", [
352 | 'table' => $table_name,
353 | 'message'=>$e->getMessage(),
354 | 'sql'=>$sql
355 | ]);
356 | }
357 | }
358 | }
--------------------------------------------------------------------------------
/src/Controller.php:
--------------------------------------------------------------------------------
1 | config = ConfigProvider::get();
32 | if (isset($this->config['enable']) && $this->config['enable'] === false) {
33 | throw new ErrorException("apidoc close");
34 | }
35 | if (!empty($this->config['request_params'])) {
36 | $this->requestParams = $this->config['request_params'];
37 | } else {
38 | $this->requestParams = (new Request())->param();
39 | }
40 | if (!empty($this->requestParams['lang']) && !empty($this->config['lang_register_function'])) {
41 | $this->lang = $this->requestParams['lang'];
42 | $this->config['lang_register_function']($this->lang);
43 | }
44 | if ($checkAuth) {
45 | (new Auth($this->config))->checkAuth($this->requestParams);
46 | }
47 | }
48 |
49 | /**
50 | * 验证权限
51 | * @param $config
52 | * @param $params
53 | */
54 | protected function checkAuth()
55 | {
56 | $config = $this->config;
57 | $params = $this->requestParams;
58 | if (!empty($params['shareKey'])) {
59 | //分享
60 | $shareData = (new ApiShare())->checkShareAuth($config, $params);
61 | $appKey = !empty($params['appKey']) ? $params['appKey'] : "";
62 | if (!empty($shareData['appKeys']) && !in_array($appKey, $shareData['appKeys'])) {
63 | throw new ErrorException("share not exists");
64 | }
65 | return $shareData;
66 | } else {
67 | (new Auth($config))->checkAuth($params);
68 | }
69 | }
70 |
71 |
72 | /**
73 | * 获取配置
74 | * @return \think\response\Json
75 | */
76 | public function getConfig()
77 | {
78 | $this->init(false);
79 | $params = $this->requestParams;
80 | if (!empty($params['shareKey'])) {
81 | // 接口分享
82 | $shareData = (new ApiShare())->checkShareAuth($this->config, $params);
83 | if (!empty($shareData['appKeys'])) {
84 | $config = ConfigProvider::getFeConfig($shareData['appKeys']);
85 | } else {
86 | $config = ConfigProvider::getFeConfig();
87 | }
88 | } else {
89 | (new Auth($this->config))->checkAuth($params);
90 | $config = ConfigProvider::getFeConfig();
91 | }
92 | return Helper::showJson(0, "", $config);
93 | }
94 |
95 | /**
96 | * 验证密码
97 | */
98 | public function verifyAuth()
99 | {
100 | $this->init();
101 | $config = $this->config;
102 | $params = $this->requestParams;
103 | if (empty($params['password'])) {
104 | throw new ErrorException("password not found");
105 | }
106 | $appKey = !empty($params['appKey']) ? $params['appKey'] : "";
107 | if (!empty($params['shareKey'])) {
108 | // 接口分享
109 | $shareData = (new ApiShare())->getShareDetailByKey($params['shareKey']);
110 | if (!empty($shareData['password']) && $params['password'] === md5($shareData['password'])) {
111 | $hasAuth = (new Auth($config))->createToken($params['password']);
112 | } else {
113 | throw new ErrorException("password error");
114 | }
115 | } else {
116 | if (!$appKey && !(!empty($config['auth']) && $config['auth']['enable'])) {
117 | throw new ErrorException("password error");
118 | }
119 | $hasAuth = (new Auth($config))->verifyAuth($params['password'], $appKey);
120 | }
121 | $res = [
122 | "token" => $hasAuth
123 | ];
124 | return Helper::showJson(0, "", $res);
125 | }
126 |
127 | /**
128 | * 获取api文档菜单
129 | */
130 | public function getApiMenus()
131 | {
132 | $this->init(false);
133 | $config = $this->config;
134 | $params = $this->requestParams;
135 | if (empty($params['appKey'])) {
136 | throw new ErrorException("appkey not found");
137 | }
138 | $appKey = $params['appKey'];
139 | $shareData = $this->checkAuth();
140 | $currentAppConfig = Helper::getCurrentAppConfig($appKey);
141 | $currentApp = $currentAppConfig['appConfig'];
142 | $apiData = $this->getApiMenusByAppKey($appKey);
143 | $groups = !empty($currentApp['groups']) ? $currentApp['groups'] : [];
144 | if (!empty($params['shareKey']) && $shareData['type'] == 'api') {
145 | $apiData['data'] = Helper::filterTreeNodesByKeys($apiData['data'], $shareData['apiKeys'], 'menuKey');
146 | }
147 | $json = [
148 | 'data' => $apiData['data'],
149 | 'app' => $currentApp,
150 | 'groups' => $groups,
151 | 'tags' => $apiData['tags'],
152 | ];
153 | return Helper::showJson(0, "", $json);
154 | }
155 |
156 | protected function getApiMenusByAppKey($appKey)
157 | {
158 | $config = $this->config;
159 | if (!empty($config['cache']) && $config['cache']['enable']) {
160 | $cacheKey = Helper::getCacheKey('apiMenu', $appKey, $this->lang);
161 | $cacheData = (new Cache())->get($cacheKey);
162 | if ($cacheData && empty($params['reload'])) {
163 | $apiData = $cacheData;
164 | } else {
165 | // 生成数据并缓存
166 | $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey);
167 | (new Cache())->set($cacheKey, $apiData);
168 | }
169 | } else {
170 | // 生成数据
171 | $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey);
172 | }
173 | return $apiData;
174 | }
175 |
176 |
177 | /**
178 | * 获取所有api文档菜单
179 | */
180 | public function getAllApiMenus()
181 | {
182 | $this->init(true);
183 | $config = $this->config;
184 | $params = $this->requestParams;
185 | $configApps = Helper::handleAppsConfig($config['apps'], false, $config);
186 | $data = ApiShare::getAppShareApis($config, $configApps, "", [], false);
187 | return Helper::showJson(0, "", $data);
188 | }
189 |
190 | /**
191 | * 获取接口明细
192 | * @return array
193 | */
194 | public function getApiDetail()
195 | {
196 | $this->init(false);
197 | $config = $this->config;
198 | $params = $this->requestParams;
199 | if (empty($params['path'])) {
200 | throw new ErrorException("path not found");
201 | }
202 | $appKey = !empty($params['appKey']) ? $params['appKey'] : "";
203 | $apiKey = urldecode($params['path']);
204 | $this->checkAuth();
205 | if (!empty($config['cache']) && $config['cache']['enable']) {
206 | $cacheKey = Helper::getCacheKey('apiDetail', $appKey, $this->lang, $params['path']);
207 | $cacheData = (new Cache())->get($cacheKey);
208 | if ($cacheData && empty($params['reload'])) {
209 | $res = $cacheData;
210 | } else {
211 | // 生成数据并缓存
212 | $res = (new ParseApiDetail($config))->renderApiDetail($appKey, $apiKey);
213 | (new Cache())->set($cacheKey, $res);
214 | }
215 | } else {
216 | // 生成数据
217 | $res = (new ParseApiDetail($config))->renderApiDetail($appKey, $apiKey);
218 | }
219 | $res['appKey'] = $appKey;
220 | return Helper::showJson(0, "", $res);
221 | }
222 |
223 |
224 | /**
225 | * 获取md菜单
226 | * @return array
227 | */
228 | public function getMdMenus()
229 | {
230 | $this->init(false);
231 | $config = $this->config;
232 | $params = $this->requestParams;
233 | $appKey = "";
234 | if (!empty($params['appKey'])) {
235 | // 获取指定应用
236 | $appKey = $params['appKey'];
237 | }
238 | $this->checkAuth();
239 | $docs = (new ParseMarkdown($config))->getDocsMenu($appKey, $this->lang);
240 | return Helper::showJson(0, "", $docs);
241 |
242 | }
243 |
244 | /**
245 | * 获取md文档内容
246 | * @return \think\response\Json
247 | */
248 | public function getMdDetail()
249 | {
250 | $this->init(false);
251 | $config = $this->config;
252 | $params = $this->requestParams;
253 | $this->checkAuth();
254 | try {
255 | if (empty($params['path'])) {
256 | throw new ErrorException("mdPath not found");
257 | }
258 | if (empty($params['appKey'])) {
259 | throw new ErrorException("appkey not found");
260 | }
261 |
262 | $path = urldecode($params['path']);
263 | $content = (new ParseMarkdown($config))->getContent($params['appKey'], $path, $this->lang);
264 | $res = [
265 | 'content' => $content,
266 | ];
267 | return Helper::showJson(0, "", $res);
268 |
269 | } catch (ErrorException $e) {
270 | return Helper::showJson($e->getCode(), $e->getMessage());
271 | }
272 | }
273 |
274 |
275 | /**
276 | * 创建代码生成器
277 | * @return array
278 | */
279 | public function createGenerator()
280 | {
281 | $this->init(true);
282 | $config = $this->config;
283 | $params = $this->requestParams;
284 | $res = (new generator\Index($config))->create($params);
285 | return Helper::showJson(0, "", $res);
286 | }
287 |
288 | /**
289 | * 删除所有接口缓存
290 | * @return array
291 | */
292 | public function cancelAllCache()
293 | {
294 | $this->init(true);
295 | $config = $this->config;
296 | $path = APIDOC_STORAGE_PATH . $config['cache']['folder']. '/apis';
297 | $res = DirAndFile::deleteDir($path);
298 | return Helper::showJson(0, "", $path);
299 | }
300 |
301 | /**
302 | * 生成所有接口缓存
303 | * @return array
304 | */
305 | public function createAllCache()
306 | {
307 | $this->init(true);
308 | $config = $this->config;
309 | $params = $this->requestParams;
310 | $apps = Helper::getAllApps($config['apps']);
311 | $cache = new Cache();
312 | DirAndFile::deleteDir(APIDOC_STORAGE_PATH . $config['cache']['folder'] . '/' . 'apis');
313 | if (!empty($apps) && count($apps)) {
314 | try {
315 | foreach ($apps as $app) {
316 | // 缓存菜单
317 | $appKey = $app['appKey'];
318 | $controllerData = (new ParseApiMenus($config))->renderApiMenus($appKey);
319 | if (!empty($controllerData['data']) && count($controllerData['data'])) {
320 | foreach ($controllerData['data'] as $controller) {
321 | if (!empty($controller['children']) && count($controller['children'])) {
322 | foreach ($controller['children'] as $item) {
323 | if (!empty($item['url']) && !empty($item['menuKey'])) {
324 | $apiDetail = (new ParseApiDetail($config))->renderApiDetail($appKey, urldecode($item['menuKey']));
325 | $apiDetailCacheKey = Helper::getCacheKey('apiDetail', $appKey, $this->lang, $item['menuKey']);
326 | $cache->set($apiDetailCacheKey, $apiDetail);
327 | }
328 | }
329 | }
330 | }
331 | }
332 | $cacheKey = Helper::getCacheKey('apiMenu', $appKey, $this->lang);
333 | $cache->set($cacheKey, $controllerData);
334 | }
335 | } catch (\ReflectionException $e) {
336 | DirAndFile::deleteDir(APIDOC_STORAGE_PATH . $config['cache']['folder'] . '/' . 'apis');
337 | throw new ErrorException($e->getMessage());
338 | }
339 | }
340 | return Helper::showJson(0, "", true);
341 | }
342 |
343 | /**
344 | * 生成代码模板内容
345 | * @return array
346 | */
347 | public function renderCodeTemplate()
348 | {
349 | $this->init(true);
350 | $config = $this->config;
351 | $params = $this->requestParams;
352 | $code = (new ParseCodeTemplate($config))->renderCode($params);
353 | return Helper::showJson(0, "", [
354 | 'code' => $code
355 | ]);
356 |
357 | }
358 |
359 |
360 | /**
361 | * 添加接口分享
362 | * @return array
363 | */
364 | public function addApiShare()
365 | {
366 | $this->init(true);
367 | $config = $this->config;
368 | $params = $this->requestParams;
369 | if (empty($params['name'])) {
370 | throw new ErrorException('field not found', ['field' => 'name']);
371 | }
372 | if (empty($params['type'])) {
373 | throw new ErrorException('field not found', ['field' => 'type']);
374 | }
375 | if ($params['type'] == 'app' && empty($params['appKeys'])) {
376 | throw new ErrorException('field not found', ['field' => 'appKeys']);
377 | }
378 | if ($params['type'] == 'api' && empty($params['apiKeys'])) {
379 | throw new ErrorException('field not found', ['field' => 'apiKeys']);
380 | }
381 | $res = (new ApiShare())->addApiShare($params);
382 | return Helper::showJson(0, "", $res);
383 | }
384 |
385 | /**
386 | * 获取接口分享分页列表
387 | * @return array
388 | */
389 | public function getApiShareList()
390 | {
391 | $this->init(true);
392 | $config = $this->config;
393 | $params = $this->requestParams;
394 | $pageIndex = !empty($params['pageIndex']) ? $params['pageIndex'] : 1;
395 | $pageSize = 5;
396 | $res = (new ApiShare())->getSharePageList($config, $pageIndex, $pageSize);
397 | return Helper::showJson(0, "", $res);
398 | }
399 |
400 | /**
401 | * 获取接口分享记录明细
402 | * @return array
403 | */
404 | public function getApiShareDetail()
405 | {
406 | $this->init(true);
407 | $config = $this->config;
408 | $params = $this->requestParams;
409 | if (empty($params['key'])) {
410 | throw new ErrorException('field not found', ['field' => 'key']);
411 | }
412 | $cacheData = (new ApiShare())->getShareDetailByKey($params['key']);
413 | return Helper::showJson(0, "", $cacheData);
414 | }
415 |
416 | /**
417 | * 删除接口分享记录
418 | * @return array
419 | */
420 | public function deleteApiShare()
421 | {
422 | $this->init(true);
423 | $config = $this->config;
424 | $params = $this->requestParams;
425 | if (empty($params['key'])) {
426 | throw new ErrorException('field not found', ['field' => 'key']);
427 | }
428 | $cacheKey = ApiShare::getShareCacheKey($params['key']);
429 | $res = (new Cache())->delete($cacheKey);
430 | return Helper::showJson(0, "", $res);
431 | }
432 |
433 | /**
434 | * 处理接口分享操作
435 | * @return array
436 | */
437 | public function handleApiShareAction()
438 | {
439 | $this->init(true);
440 | $config = $this->config;
441 | $params = $this->requestParams;
442 | if (empty($params['key'])) {
443 | throw new ErrorException('field not found', ['field' => 'key']);
444 | }
445 | if (!isset($params['index'])) {
446 | throw new ErrorException('field not found', ['field' => 'index']);
447 | }
448 | $res = (new ApiShare())->handleApiShareAction($config, $params['key'], $params['index']);
449 | return Helper::showJson(0, "", $res);
450 | }
451 |
452 | /**
453 | * 导出swagger.json
454 | * @return array
455 | */
456 | public function exportSwagger()
457 | {
458 |
459 | $this->init(true);
460 | $config = $this->config;
461 | $params = $this->requestParams;
462 | if($config['export_config']['enable'] === false){
463 | throw new ErrorException('export config not enable');
464 | }
465 | if (empty($params['key'])) {
466 | throw new ErrorException('field not found', ['field' => 'key']);
467 | }
468 | $searchData = (new ApiShare())->getShareData($config,$params['key']);
469 |
470 | $res = (new ExportSwagger($config['export_config']))->exportJson($config,$searchData);
471 | return Helper::showJson(0, "", $res);
472 | }
473 |
474 | }
475 |
--------------------------------------------------------------------------------