This commit is contained in:
wangjinlei
2022-04-06 18:02:49 +08:00
parent e34f87de36
commit c1885928ff
262 changed files with 18633 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
name: 'GitHub Actions Mirror'
on: [push]
jobs:
mirror_to_gitee:
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v1
- name: 'Mirror to gitee'
uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url:
git@gitee.com:hg-code/thinkphp-apidoc.git
ssh_private_key:
${{ secrets.GITEE_KEY }}

1
vendor/hg/apidoc/.gitignore vendored Normal file
View File

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

21
vendor/hg/apidoc/LICENSE vendored Normal file
View File

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

79
vendor/hg/apidoc/README.md vendored Normal file
View File

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

43
vendor/hg/apidoc/composer.json vendored Normal file
View File

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

172
vendor/hg/apidoc/src/Auth.php vendored Normal file
View File

@@ -0,0 +1,172 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc;
use think\facade\Config;
use think\facade\Request;
use hg\apidoc\exception\AuthException;
class Auth
{
protected $config = [];
public function __construct()
{
$this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
}
/**
* 验证密码
* @param $password
* @return false|string
*/
public function verifyAuth(string $password, string $appKey)
{
if (!empty($appKey)) {
$currentApps = (new Utils())->getCurrentApps($appKey);
$currentApp = $currentApps[count($currentApps) - 1];
if (!empty($currentApp) && !empty($currentApp['password'])) {
// 应用密码
if (md5($currentApp['password']) === $password) {
return $this->createToken($currentApp['password']);
}
throw new AuthException("password error");
}
}
if ($this->config['auth']['enable']) {
// 密码验证
if (md5($this->config['auth']['password']) === $password) {
return $this->createToken($this->config['auth']['password']);
}
throw new AuthException("password error");
}
return false;
}
/**
* 获取tokencode
* @param string $password
* @return string
*/
protected function getTokenCode(string $password): string
{
return md5(md5($password) . strtotime(date('Y-m-d', time())));
}
/**
* 创建token
* @param string $password
* @return string
*/
public function createToken(string $password): string
{
return $this->handleToken($this->getTokenCode($password), "CE");
}
/**
* 验证token
* @param $token
* @return bool
*/
public function checkToken(string $token, string $password): bool
{
if (empty($password)) {
$password = $this->config['auth']['password'];
}
$decode = $this->handleToken($token, "DE");
if ($decode === $this->getTokenCode($password)) {
return true;
}
return false;
}
/**
* @param $request
* @return bool
*/
public function checkAuth(string $appKey): bool
{
$config = $this->config;
$request = Request::instance();
$token = $request->param("apidocToken");
if (!empty($appKey)) {
$currentApps = (new Utils())->getCurrentApps($appKey);
$currentApp = $currentApps[count($currentApps) - 1];
if (!empty($currentApp) && !empty($currentApp['password'])) {
if (empty($token)) {
throw new AuthException("token not found");
}
// 应用密码
if ($this->checkToken($token, $currentApp['password'])) {
return true;
} else {
throw new AuthException("token error");
}
} else if (!(!empty($config['auth']) && $config['auth']['enable'])) {
return true;
}
}
if(!empty($config['auth']) && $config['auth']['enable'] && empty($token)){
throw new AuthException("token not found");
}else if (!empty($token) && !$this->checkToken($token, "")) {
throw new AuthException("token error");
}
return true;
}
/**
* 处理token
* @param $string
* @param string $operation
* @param string $key
* @param int $expiry
* @return false|string
*/
protected function handleToken(string $string, string $operation = 'DE', string $key = '', int $expiry = 0):string
{
$ckey_length = 4;
$key = md5($key ? $key : $this->config['auth']['secret_key']);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya . md5($keya . $keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for ($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for ($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for ($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if ($operation == 'DE') {
if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc . str_replace('=', '', base64_encode($result));
}
}
}

259
vendor/hg/apidoc/src/Controller.php vendored Normal file
View File

@@ -0,0 +1,259 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc;
use hg\apidoc\exception\AuthException;
use hg\apidoc\exception\ErrorException;
use hg\apidoc\parseApi\CacheApiData;
use hg\apidoc\parseApi\ParseAnnotation;
use hg\apidoc\parseApi\ParseMarkdown;
use think\App;
use think\facade\Config;
use think\facade\Request;
use hg\apidoc\crud\CreateCrud;
class Controller
{
protected $app;
protected $config;
protected $defaultConfig=[
'crud'=>[
'model'=>[
'fields_types'=>[
"int",
"tinyint",
"smallint",
"mediumint",
"integer",
"bigint",
"bit",
"real",
"float",
"decimal",
"numeric",
"char",
"varchar",
"date",
"time",
"year",
"timestamp",
"datetime",
"tinyblob",
"blob",
"mediumblob",
"longblob",
"tinytext",
"text",
"mediumtext",
"longtext",
"enum",
"set",
"binary",
"varbinary",
"point",
"linestring",
"polygon",
"geometry",
"multipoint",
"multilinestring",
"multipolygon",
"geometrycollection"
]
]
]
];
public function __construct(App $app)
{
$this->app = $app;
$this->config = Config::get("apidoc")?Config::get("apidoc"):Config::get("apidoc.");
}
/**
* 获取配置
* @return \think\response\Json
*/
public function getConfig(){
$config = $this->config;
if (!empty($config['auth'])){
unset($config['auth']['auth_password']);
unset($config['auth']['password']);
unset($config['auth']['key']);
}
// 处理统一返回信息
if (!empty($config['responses']) && is_string($config['responses'])){
// 兼容原配置
$config['responses'] = [
'jsonStr'=>$config['responses']
];
}else if (!empty($config['responses']) && isset($config['responses']['show_responses']) && !$config['responses']['show_responses'] && !empty($config['responses']['data'])){
// 显示在提示中
$responsesStr = '{'."\r\n";
$responsesMain = "";
foreach ($config['responses']['data'] as $item){
$responsesStr.='"'.$item['name'].'":"'.$item['desc'].'",'."\r\n";
if (!empty($item['main']) && $item['main']==true){
$responsesMain = $item;
}
}
$responsesStr.= '}';
$config['responses']['jsonStr']=$responsesStr;
$config['responses']['main']=$responsesMain;
}
$config['debug']=app()->isDebug();
if (!empty($config['crud'])){
// 无配置可选字段类型,使用默认的
if (!empty($config['crud']['model']) && empty($config['crud']['model']['fields_types'])){
$config['crud']['model']['fields_types'] = $this->defaultConfig['crud']['model']['fields_types'];
}
// 过滤route文件配置
if (!empty($config['crud']['route'])){
unset($config['crud']['route']);
}
}
// 清除apps配置中的password
$config['apps'] = (new Utils())->handleAppsConfig($config['apps']);
return Utils::showJson(0,"",$config);
}
/**
* 验证密码
* @return false|\think\response\Json
* @throws \think\Exception
*/
public function verifyAuth(){
$config = $this->config;
$request = Request::instance();
$params = $request->param();
$password = $params['password'];
if (empty($password)){
throw new AuthException( "password not found");
}
$appKey = !empty($params['appKey'])?$params['appKey']:"";
if (!$appKey && !(!empty($config['auth']) && $config['auth']['enable'])) {
return false;
}
try {
$hasAuth = (new Auth())->verifyAuth($password,$appKey);
return Utils::showJson(0,"",$hasAuth);
} catch (AuthException $e) {
return Utils::showJson($e->getCode(),$e->getMessage());
}
}
/**
* 获取文档数据
* @return \think\response\Json
*/
public function getApidoc(){
$config = $this->config;
$request = Request::instance();
$params = $request->param();
(new Auth())->checkAuth($params['appKey']);
$cacheData=null;
if (!empty($config['cache']) && $config['cache']['enable']){
$cacheData = (new CacheApiData())->get($params['appKey'],$params['cacheFileName']);
if ($cacheData && $params['reload']=='false'){
$apiData = $cacheData['data'];
}else{
// 生成数据并缓存
$apiData = (new ParseAnnotation())->renderApiData($params['appKey']);
$cacheData =(new CacheApiData())->set($params['appKey'],$apiData);
}
}else{
// 生成数据
$apiData = (new ParseAnnotation())->renderApiData($params['appKey']);
}
$groups=[['title'=>'全部','name'=>0]];
// 获取md
$docs = (new ParseMarkdown())->getDocsMenu();
if (count($docs)>0){
$menu_title = !empty($config['docs']) && !empty($config['docs']['menu_title'])?$config['docs']['menu_title']:'文档';
$groups[]=['title'=>$menu_title,'name'=>'markdown_doc'];
}
if (!empty($config['groups']) && count($config['groups'])>0 && !empty($apiData['groups']) && count($apiData['groups'])>0){
$configGroups=[];
foreach ($config['groups'] as $group) {
if (in_array($group['name'],$apiData['groups'])){
$configGroups[]=$group;
}
}
$groups = array_merge($groups,$configGroups);
}
$json=[
'groups'=>$groups,
'list'=>$apiData['data'],
'docs'=>$docs,
'tags'=>$apiData['tags']
];
if ($cacheData && !empty($cacheData['list'])){
$json['cacheFiles']=$cacheData['list'];
$json['cacheName']=$cacheData['name'];
}
return Utils::showJson(0,"",$json);
}
/**
* 获取md文档内容
* @return \think\response\Json
*/
public function getMdDetail(){
$request = Request::instance();
$params = $request->param();
try {
if (empty($params['path'])){
throw new ErrorException("mdPath not found");
}
if (empty($params['appKey'])){
throw new ErrorException("appkey not found");
}
(new Auth())->checkAuth($params['appKey']);
$res = (new ParseMarkdown())->getContent($params['appKey'],$params['path']);
return Utils::showJson(0,"",$res);
} catch (ErrorException $e) {
return Utils::showJson($e->getCode(),$e->getMessage());
}
}
/**
* 创建Crud
* @return \think\response\Json
*/
public function createCrud()
{
if (!\think\facade\App::isDebug()) {
throw new ErrorException("no debug",501);
}
$request = Request::instance();
$params = $request->param();
if (!isset($params['appKey'])){
throw new ErrorException("appkey not found");
}
(new Auth())->checkAuth($params['appKey']);
$res = (new CreateCrud())->create($params);
return Utils::showJson(0,"",$res);
}
}

35
vendor/hg/apidoc/src/Service.php vendored Normal file
View File

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

330
vendor/hg/apidoc/src/Utils.php vendored Normal file
View File

@@ -0,0 +1,330 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc;
use hg\apidoc\exception\ErrorException;
use think\facade\Config;
use think\response\Json;
class Utils
{
protected static $snakeCache = [];
/**
* 统一返回json格式
* @param int $code
* @param string $msg
* @param string $data
* @return \think\response\Json
*/
public static function showJson(int $code = 0, string $msg = "", $data = ""):Json
{
$res = [
'code' => $code,
'msg' => $msg,
'data' => $data,
];
return json($res);
}
/**
* 过滤参数字段
* @param $data
* @param $fields
* @param string $type
* @return array
*/
public function filterParamsField(array $data, $fields, string $type = "field"): array
{
if ($fields && strpos($fields, ',') !== false){
$fieldArr = explode(',', $fields);
}else{
$fieldArr = [$fields];
}
$dataList = [];
foreach ($data as $item) {
if (!empty($item['name']) && in_array($item['name'], $fieldArr) && $type === 'field') {
$dataList[] = $item;
} else if (!(!empty($item['name']) && in_array($item['name'], $fieldArr)) && $type == "withoutField") {
$dataList[] = $item;
}
}
return $dataList;
}
/**
* 读取文件内容
* @param $fileName
* @return false|string
*/
public static function getFileContent(string $fileName): string
{
$content = "";
if (file_exists($fileName)) {
$handle = fopen($fileName, "r");
$content = fread($handle, filesize($fileName));
fclose($handle);
}
return $content;
}
/**
* 保存文件
* @param $path
* @param $str_tmp
* @return bool
*/
public static function createFile(string $path, string $str_tmp): bool
{
$pathArr = explode("/", $path);
unset($pathArr[count($pathArr) - 1]);
$dir = implode("/", $pathArr);
if (!file_exists($dir)) {
mkdir($dir, 0777, true);
}
$fp = fopen($path, "w") or die("Unable to open file!");
fwrite($fp, $str_tmp); //存入内容
fclose($fp);
return true;
}
/**
* 删除文件
* @param $path
*/
public static function delFile(string $path)
{
$url = iconv('utf-8', 'gbk', $path);
if (PATH_SEPARATOR == ':') { //linux
unlink($path);
} else { //Windows
unlink($url);
}
}
/**
* 将tree树形数据转成list数据
* @param array $tree tree数据
* @param string $childName 子节点名称
* @return array 转换后的list数据
*/
public function treeToList(array $tree, string $childName = 'children',string $key = "id",string $parentField = "parent")
{
$array = array();
foreach ($tree as $val) {
$array[] = $val;
if (isset($val[$childName])) {
$children = $this->treeToList($val[$childName], $childName);
if ($children) {
$newChildren = [];
foreach ($children as $item) {
$item[$parentField] = $val[$key];
$newChildren[] = $item;
}
$array = array_merge($array, $newChildren);
}
}
}
return $array;
}
/**
* 根据一组keys获取所有关联节点
* @param $tree
* @param $keys
*/
public function getTreeNodesByKeys(array $tree, array $keys, string $field = "id", string $childrenField = "children")
{
$list = $this->TreeToList($tree, $childrenField, "folder");
$data = [];
foreach ($keys as $k => $v) {
$parent = !$k ? "" : $keys[$k - 1];
foreach ($list as $item) {
if (((!empty($item['parent']) && $item['parent'] === $parent) || empty($item['parent'])) && $item[$field] == $v) {
$data[] = $item;
break;
}
}
}
return $data;
}
/**
* 替换模板变量
* @param $temp
* @param $data
* @return string|string[]
*/
public static function replaceTemplate(string $temp, array $data):string
{
$str = $temp;
foreach ($data as $k => $v) {
$key = '${' . $k . '}';
if (strpos($str, $key) !== false) {
$str = str_replace($key, $v, $str);
}
}
return $str;
}
/**
* 替换当前所选应用/版本的变量
* @param $temp
* @param $currentApps
* @return string|string[]
*/
public function replaceCurrentAppTemplate(string $temp,array $currentApps):string
{
$str = $temp;
if (!empty($currentApps) && count($currentApps) > 0) {
$data = [];
for ($i = 0; $i <= 3; $i++) {
if (isset($currentApps[$i])) {
$appItem = $currentApps[$i];
foreach ($appItem as $k => $v) {
$key = 'app[' . $i . '].' . $k;
$data[$key] = $v;
}
} else {
$appItem = $currentApps[0];
foreach ($appItem as $k => $v) {
$key = 'app[' . $i . '].' . $k;
$data[$key] = "";
}
}
}
$str = $this->replaceTemplate($str, $data);
}
return $str;
}
/**
* 根据条件获取数组中的值
* @param array $array
* @param $query
* @return mixed|null
*/
public static function getArrayFind(array $array, $query)
{
$res = null;
if (is_array($array)) {
foreach ($array as $item) {
if ($query($item)) {
$res = $item;
break;
}
}
}
return $res;
}
/**
* 合并对象数组并根据key去重
* @param string $name
* @param mixed ...$array
* @return array
*/
public static function arrayMergeAndUnique(string $key = "name", ...$array):array
{
$mergeArr = [];
foreach ($array as $k => $v) {
$mergeArr = array_merge($mergeArr, $v);
}
$keys = [];
foreach ($mergeArr as $k => $v) {
$keys[] = $v[$key];
}
$uniqueKeys = array_flip(array_flip($keys));
$newArray = [];
foreach ($uniqueKeys as $k => $v) {
$newArray[] = $mergeArr[$k];
}
return $newArray;
}
/**
* 初始化当前所选的应用/版本数据
* @param $appKey
*/
public function getCurrentApps(string $appKey):array
{
$config = Config::get("apidoc")?Config::get("apidoc"):Config::get("apidoc.");
if (!(!empty($config['apps']) && count($config['apps']) > 0)) {
throw new ErrorException("no config apps", 500);
}
if (strpos($appKey, '_') !== false) {
$keyArr = explode("_", $appKey);
} else {
$keyArr = [$appKey];
}
$currentApps = $this->getTreeNodesByKeys($config['apps'], $keyArr, 'folder', 'items');
if (!$currentApps) {
throw new ErrorException("appKey error", 412, [
'appKey' => $appKey
]);
}
return $currentApps;
}
/**
* 处理apps配置参数
* @param array $apps
* @return array
*/
public function handleAppsConfig(array $apps):array
{
$appsConfig = [];
foreach ($apps as $app) {
if (!empty($app['password'])) {
unset($app['password']);
$app['hasPassword'] = true;
}
if (!empty($app['items']) && count($app['items']) > 0) {
$app['items'] = $this->handleAppsConfig($app['items']);
}
$appsConfig[] = $app;
}
return $appsConfig;
}
/**
* 驼峰转下划线
*
* @param string $value
* @param string $delimiter
* @return string
*/
public static function snake(string $value, string $delimiter = '_'): string
{
$key = $value;
if (isset(static::$snakeCache[$key][$delimiter])) {
return static::$snakeCache[$key][$delimiter];
}
if (!ctype_lower($value)) {
$value = preg_replace('/\s+/u', '', $value);
$value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
}
return static::$snakeCache[$key][$delimiter] = $value;
}
/**
* 字符串转小写
*
* @param string $value
* @return string
*/
public static function lower(string $value): string
{
return mb_strtolower($value, 'UTF-8');
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

14
vendor/hg/apidoc/src/annotation/Tag.php vendored Normal file
View File

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

View File

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

14
vendor/hg/apidoc/src/annotation/Url.php vendored Normal file
View File

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

View File

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

62
vendor/hg/apidoc/src/config.php vendored Normal file
View File

@@ -0,0 +1,62 @@
<?php
return [
// 文档标题
'title' => 'APi接口文档',
// 文档描述
'desc' => '',
// 版权申明
'copyright' => 'Powered By hg-code',
// 默认作者
'default_author'=>'',
// 默认请求类型
'default_method'=>'GET',
// 设置应用/版本(必须设置)
'apps' => [
['title'=>'v1.0','path'=>'app\controller','folder'=>'v1'],
],
// 控制器分组
'groups' => [],
// 指定公共注释定义的文件地址
'definitions' => "app\controller\Definitions",
//指定生成文档的控制器
'controllers' => [],
// 过滤,不解析的控制器
'filter_controllers' => [],
// 缓存配置
'cache' => [
// 是否开启缓存
'enable' => false,
// 缓存文件路径
'path' => '../runtime/apidoc/',
// 是否显示更新缓存按钮
'reload' => true,
// 最大缓存文件数
'max' => 5, //最大缓存数量
],
// 权限认证配置
'auth' => [
// 是否启用密码验证
'enable' => false,
// 验证密码
'password' => "123456",
// 密码加密盐
'secret_key' => "apidoc#hg_code",
],
// 统一的请求Header
'headers'=>[],
// 统一的请求参数Parameters
'parameters'=>[],
// 统一的请求响应体,仅显示在文档提示中
'responses'=>[
['name'=>'code','desc'=>'状态码','type'=>'int'],
['name'=>'message','desc'=>'操作描述','type'=>'string'],
['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object'],
],
// md文档
'docs' => [
'menu_title' => '开发文档',
'menus' => []
],
'crud'=>[]
];

383
vendor/hg/apidoc/src/crud/CreateCrud.php vendored Normal file
View File

@@ -0,0 +1,383 @@
<?php
namespace hg\apidoc\crud;
use hg\apidoc\exception\ErrorException;
use think\Db as Db5;
use think\facade\Config;
use think\facade\Db;
use think\facade\App;
use hg\apidoc\Utils;
class CreateCrud
{
protected $config;
protected $currentApps;
protected $controller_layer = "";
public function __construct()
{
$config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
$this->controller_layer = Config::get('route.controller_layer',"controller");
if (!empty($config) && !empty($config['crud'])){
$this->config = $config['crud'];
}else{
throw new ErrorException("no config crud",501);
}
}
/**
* 创建Crud
* @param $params
* @return array
*/
public function create($params){
$appKey = $params['appKey'];
$currentApps = (new Utils())->getCurrentApps($appKey);
// $currentApp = $currentApps[count($currentApps) - 1];
$data = $this->renderTemplateData($params,$currentApps);
$res = [];
// 创建数据表
if (!empty($params['model']['table'])){
$sqlRes = $this->createModelTable($params['model'],$params['title']);
if ($sqlRes == true){
$res[]="创建数据表成功";
}else{
// $msg= $sqlRes?$sqlRes:"数据表创建失败,请检查配置";
throw new ErrorException("datatable crud error",500);
}
}
// 生成文件
$keys = array_keys($this->config);
foreach ($keys as $index=>$key) {
// 添加路由
if (
$key==="route" &&
!empty($this->config['route']) &&
!empty($this->config['route']['template']) &&
!empty($this->config['route']['path'])
){
$tmp_path = (new Utils())->replaceCurrentAppTemplate($this->config['route']['template'],$currentApps);
$tempPath = $tmp_path.".txt";
$str_tmp = Utils::getFileContent($tempPath);
if (!empty($str_tmp)){
$tmp_content = Utils::replaceTemplate($str_tmp,$data);
$tmp_content = (new Utils())->replaceCurrentAppTemplate($tmp_content,$currentApps);
$routePathStr = (new Utils())->replaceCurrentAppTemplate($this->config['route']['path'],$currentApps);
$routePathStr = str_replace("\\","/",$routePathStr);
$routePath = App::getAppPath().$routePathStr;
$routeContent = Utils::getFileContent($routePath);
$routeContent.="\r\n".$tmp_content."\r\n";
Utils::createFile($routePath,$routeContent);
$res[]="添加路由成功";
}
}else{
$currentConfig = $this->config[$key];
$currentParam = $params[$key];
$tmp_path = (new Utils())->replaceCurrentAppTemplate($currentParam['template'],$currentApps);
$tempPath = $tmp_path.".txt";
$str_tmp = Utils::getFileContent($tempPath);
$file_content = Utils::replaceTemplate($str_tmp,$data);
$file_content = (new Utils())->replaceCurrentAppTemplate($file_content,$currentApps);
$namespacePath = str_replace("\\","/",$currentParam['path']);
$fileName = $data[$key.'.file_name'];
$filePath = '../'.$namespacePath.'/'.$fileName.'.php';
$fp=Utils::createFile($filePath,$file_content);
if ($fp){
$res[]="创建文件成功 path:".$filePath;
}
}
}
return $res;
}
/**
* 生成模板变量的数据
* @param $params
* @return array
*/
public function renderTemplateData($params,array $currentApps){
// 模板参数
$api_group = "";
if (!empty($params['group'])){
$api_group = '@Apidoc\Group("'.$params['group'].'")';
}
$data=[
'title'=>!empty($params['title'])?$params['title']:"",
'api_group'=>$api_group,
];
$keys = array_keys($this->config);
foreach ($keys as $index=>$key){
$currentConfig = $this->config[$key];
//验证模板是否存在
$tmp_path = (new Utils())->replaceCurrentAppTemplate($currentConfig['template'],$currentApps);
if(!file_exists($tmp_path.'.txt')){
throw new ErrorException("template not found",404,[
'template'=>$currentConfig['template']
]);
}
if ($key==="route"){
continue;
}
$currentParam = $params[$key];
if(!preg_match("/^[A-Z]{1}[A-Za-z0-9]{1,32}$/",$currentParam['class_name'])){
throw new ErrorException("classname error",412,[
'classname'=>$currentParam['class_name']
]);
}
$currentParamPath = str_replace("\\","/",$currentParam['path']);
// 验证目录是否存在
if(!is_dir('../'.$currentParamPath)){
throw new ErrorException("path not found",404,[
'path'=>$currentParamPath
]);
}
$appPath = App::getAppPath();
$appPathArr = explode("\\", $appPath);
$appFolder = $appPathArr[count($appPathArr)-1]?$appPathArr[count($appPathArr)-1]:$appPathArr[count($appPathArr)-2];
$namespace = str_replace($appFolder, App::getNamespace(), $currentParam['path']);
if ($key==="controller"){
$pathArr = explode("\\", $namespace);
$notArr = array(App::getNamespace(), $this->controller_layer);
$url = "/";
foreach ($pathArr as $pathItem){
if (!in_array($pathItem,$notArr)){
$url.=$pathItem."/";
}
}
$data['folder']=$url;
$data['api_class_name']=lcfirst($currentParam['class_name']);
$data['api_url']=$url.lcfirst($currentParam['class_name']);
}else if ($key==="model" && !empty($currentParam['table'])){
// 模型
// 获取主键
foreach ($currentParam['table'] as $item){
if ($item['main_key']==true){
$data['main_key.field'] = $item['field'];
$data['main_key.type'] = $item['type'];
$data['main_key.desc'] = $item['desc'];
break;
}
}
$tp_version = \think\facade\App::version();
if (substr($tp_version, 0, 2) == '5.') {
$table_prefix = Config::get('database.prefix');
}else{
$driver = Config::get('database.default');
$table_prefix = Config::get('database.connections.' . $driver . '.prefix');
}
$data[$currentParam['name'] . '.table_prefix'] = $table_prefix;
$table_name = Utils::snake($currentParam['class_name']);
$data[$currentParam['name'].'.table_name']=$table_name;
}
$namespace = str_replace($appFolder, App::getNamespace(), $currentParam['path']);
$data[$currentParam['name'].'.class_name']=$currentParam['class_name'];
$data[$currentParam['name'].'.namespace']=$namespace;
// 验证文件是否已存在
$fileName = $currentParam['class_name'];
if (!empty($currentConfig['file_name'])){
$fileName = (new Utils())->replaceTemplate($currentConfig['file_name'],$data);
}
$filePath = '../'.$currentParamPath.'/'.$fileName.'.php';
$data[$currentParam['name'].'.file_name']=$fileName;
if(file_exists($filePath)){
throw new ErrorException("file already exists",500,[
'filepath'=>$filePath
]);
}
$data[$currentParam['name'].'.use_path']=$namespace."\\".$fileName;
$data[$currentParam['name'].'.use_alias']=$fileName.ucwords($currentParam['name']);
}
// 字段过滤数据
if (!empty($params['model']['table'])){
$checkKeys = ['list','detail','add','edit'];
foreach ($checkKeys as $checkKey){
$itemArr = ['field'=>[],'withoutField'=>[]];
foreach ($params['model']['table'] as $item){
if ($item[$checkKey]){
$itemArr['field'][]=$item['field'];
}else{
$itemArr['withoutField'][]=$item['field'];
}
}
$data[$checkKey.'.field']=implode(",", $itemArr['field']);
$data[$checkKey.'.withoutField']=implode(",", $itemArr['withoutField']);
}
// 查询条件、验证规则
$query_where='$where=[];'."\r\n";
$query_annotation = "";
$validate_rule = "["."\r\n";
$validate_message = "["."\r\n";
$validate_fields = [];
$addRequireFields = [];
foreach ($params['model']['table'] as $i=>$item){
if ($item['query']){
$itemArr['field'][]=$item['field'];
$query_where.= ' if(!empty($param[\''.$item['field'].'\'])){'."\r\n";
$query_where.= ' $where[] = [\''.$item['field'].'\',\'=\',$param[\''.$item['field'].'\']];'."\r\n";
$query_where.= ' }'."\r\n";
$fh = empty($query_annotation)?'* ':' * ';
$require = $item['not_null']==true?'true':'false';
$rn="";
if (($i+1)<count($params['model']['table'])){
$rn="\r\n";
}
$query_annotation.=$fh.'@Apidoc\Param("'.$item['field'].'",type="'.$item['type'].'",require='.$require.',desc="'.$item['desc'].'")'.$rn;
}
// 验证规则
if (!empty($this->config['validate'])){
// 存在配置验证规则
if (!empty($item['validate']) && $this->config['validate']['rules']){
$rulesConfig = $this->config['validate']['rules'];
$currentRuleConfig = "";
foreach ($rulesConfig as $rule){
if ($rule['rule'] === $item['validate']){
$currentRuleConfig = $rule;
break;
}
}
if (!empty($currentRuleConfig)){
$validate_rule.=' \''.$item['field'].'\' => \''.$item['validate'].'\','."\r\n";
if (!empty($currentRuleConfig['message']) ){
if (is_array($currentRuleConfig['message']) && count($currentRuleConfig['message'])>0){
foreach ($currentRuleConfig['message'] as $ruleKey=>$ruleMessage){
if ($ruleKey=='0'){
$ruleKeyStr = $item['field'];
}else{
$ruleKeyStr = Utils::replaceTemplate($ruleKey,$item);
}
$ruleMessageStr = Utils::replaceTemplate($ruleMessage,$item);
$validate_message.=' \''.$ruleKeyStr.'\' => \''.$ruleMessageStr.'\','."\r\n";
}
}else{
$ruleMessageStr = Utils::replaceTemplate($currentRuleConfig['message'],$item);
$validate_message.=' \''.$item['field'].'\' => \''.$ruleMessageStr.'\','."\r\n";
}
}
$validate_fields[]=$item['field'];
if($item['field'] !== $data['main_key.field']){
$addRequireFields[]=$item['field'];
}
}
}else if($item['not_null']){
$validate_fields[]=$item['field'];
if($item['field'] !== $data['main_key.field']){
$addRequireFields[]=$item['field'];
}
// 根据not_null生成必填
$validate_rule.=' \''.$item['field'].'\' => \'require\','."\r\n";
$validate_message.=' \''.$item['field'].'\' => \''.$item['field'].'不可为空\','."\r\n";
}
}
}
$validate_rule.=' ];'."\r\n";
$validate_message.=' ];'."\r\n";
if (!empty($this->config['validate'])) {
$data['validate.rule'] = $validate_rule;
$data['validate.message'] = $validate_message;
$data['validate.scene.edit'] = json_encode($validate_fields);
$data['validate.scene.add'] = json_encode($addRequireFields)=='[]'?'[\'\']':json_encode($addRequireFields);
$data['validate.scene.delete'] = '[\'' . $data['main_key.field'] . '\']';
}
$data['query.where']=$query_where;
$data['query.annotation']=$query_annotation;
}
return $data;
}
/**
* 创建数据表
* @return mixed
*/
public function createModelTable($params,$title=""){
$data = $params['table'];
if (empty($title)){
$title =$params['class_name'];
}
$driver = Config::get('database.default');
$table_prefix=Config::get('database.connections.'.$driver.'.prefix');
$table_name = $table_prefix.Utils::snake($params['class_name']);
$table_data = '';
$main_keys = '';
foreach ($data as $item){
$table_field="`".$item['field']."` ".$item['type'];
if (!empty($item['length'])){
$table_field.="(".$item['length'].")";
}
if ($item['main_key']){
$main_keys.=$item['field'];
$table_field.=" NOT NULL";
}else if ($item['not_null']){
$table_field.=" NOT NULL";
}
if ($item['incremental']==true){
$table_field.=" AUTO_INCREMENT";
}
if (!empty($item['default']) || $item['default']=="0"){
$table_field.=" DEFAULT '".$item['default']."'";
}
$table_field.=" COMMENT '".$item['desc']."',";
$table_data.=$table_field;
}
$sql = "CREATE TABLE IF NOT EXISTS `$table_name` (
$table_data
PRIMARY KEY (`$main_keys`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='$title' AUTO_INCREMENT=1 ;";
$tp_version = \think\facade\App::version();
if (substr($tp_version, 0, 2) == '5.'){
Db5::startTrans();
try {
Db5::query($sql);
// 提交事务
Db5::commit();
return true;
} catch (\Exception $e) {
// 回滚事务
Db5::rollback();
return $e->getMessage();
}
}else{
Db::startTrans();
try {
Db::query($sql);
// 提交事务
Db::commit();
return true;
} catch (\Exception $e) {
// 回滚事务
Db::rollback();
return $e->getMessage();
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc\parseApi;
use hg\apidoc\Utils;
use think\facade\Config;
class CacheApiData
{
protected $config = [];
public function __construct()
{
$this->config = Config::get('apidoc');
}
/**
* 获取缓存目录
* @param string $appKey
* @return string
*/
protected function getCacheFolder(string $appKey):string
{
$config = $this->config;
$currentApps = (new Utils())->getCurrentApps($appKey);
$configPath = !empty($config['cache']) && !empty($config['cache']['path']) ? $config['cache']['path'] : '../runtime/apidoc/';
$cacheAppFolder = "";
if (!empty($currentApps) && count($currentApps) > 0) {
foreach ($currentApps as $keyIndex => $appItem) {
$cacheAppFolder .= $appItem['folder'] . "/";
}
}
$cacheFolder = $configPath . $cacheAppFolder;
return $cacheFolder;
}
/**
* 获取指定目录下缓存文件名列表
* @param string $folder
* @return array
*/
public function getCacheFileList(string $folder):array
{
$filePaths = glob($folder . '*.json');
$cacheFiles = [];
if (count($filePaths) > 0) {
foreach ($filePaths as $item) {
$cacheFiles[] = str_replace(".json", "", basename($item));
}
}
return $cacheFiles;
}
/**
* 获取接口缓存数据
* @param string $appKey
* @param string $cacheFileName
* @return array|false
*/
public function get(string $appKey, string $cacheFileName)
{
$cacheFolder = $this->getCacheFolder($appKey);
$cacheFileList = $this->getCacheFileList($cacheFolder);
if (!file_exists($cacheFolder)) {
return false;
}
if (empty($cacheFileName) && count($cacheFileList) > 0) {
// 默认最后一个缓存文件
$cacheFileName = $cacheFileList[count($cacheFileList) - 1];
}
$cacheFilePath = $cacheFolder . "/" . $cacheFileName . '.json';
if (file_exists($cacheFilePath)) {
// 存在缓存文件
$fileContent = file_get_contents($cacheFilePath);
if (empty($fileContent)) {
return false;
}
$json = json_decode($fileContent);
if (is_object($json)) {
$json = [
"data" => $json->data,
"tags" => $json->tags,
"groups" => $json->groups,
];
}
return [
'name' => $cacheFileName,
'data' => $json,
'list' => $cacheFileList
];
}
return false;
}
/**
* 设置接口缓存
* @param string $appKey
* @param array $json
* @return array|false
*/
public function set(string $appKey, array $json):array
{
if (empty($json)) {
return false;
}
$config = $this->config;
$fileName = date("Y-m-d H_i_s");
$fileContent = json_encode($json);
$cacheFolder = $this->getCacheFolder($appKey);
$path = $cacheFolder . $fileName . ".json";
Utils::createFile($path, $fileContent);
$filePaths = $this->getCacheFileList($cacheFolder);
if ($config['cache']['max'] && count($filePaths) >= $config['cache']['max']) {
//达到最大数量,删除第一个
$filePath = $cacheFolder . $filePaths[0] . ".json";
Utils::delFile($filePath);
}
return [
"name" => $fileName,
"data" => $json,
"list" => $this->getCacheFileList($cacheFolder)
];
}
}

View File

@@ -0,0 +1,663 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc\parseApi;
use Doctrine\Common\Annotations\AnnotationReader;
use hg\apidoc\Utils;
use ReflectionClass;
use Symfony\Component\ClassLoader\ClassMapGenerator;
use think\annotation\route\Resource;
use think\annotation\Route;
use hg\apidoc\annotation\Group;
use hg\apidoc\annotation\Sort;
use hg\apidoc\annotation\Param;
use hg\apidoc\annotation\Title;
use hg\apidoc\annotation\Desc;
use hg\apidoc\annotation\Author;
use hg\apidoc\annotation\Tag;
use hg\apidoc\annotation\Header;
use hg\apidoc\annotation\Returned;
use hg\apidoc\annotation\ParamType;
use hg\apidoc\annotation\Url;
use hg\apidoc\annotation\Method;
use think\annotation\route\Group as RouteGroup;
use think\facade\App;
use think\facade\Config;
class ParseAnnotation
{
protected $config = [];
protected $reader;
//tags当前应用/版本所有的tag
protected $tags = array();
//groups,当前应用/版本的分组name
protected $groups = array();
protected $controller_layer = "";
public function __construct()
{
$this->reader = new AnnotationReader();
$this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
$this->controller_layer = Config::get('route.controller_layer',"controller");
}
/**
* 生成api接口数据
* @param string $appKey
* @return array
*/
public function renderApiData(string $appKey): array
{
$config = $this->config;
$currentApps = (new Utils())->getCurrentApps($appKey);
$currentApp = $currentApps[count($currentApps) - 1];
if (!empty($config['controllers']) && count($config['controllers']) > 0) {
// 配置的控制器列表
$controllers = $this->getConfigControllers($currentApp['path']);
} else {
// 默认读取所有的
$controllers = $this->getDirControllers($currentApp['path']);
}
$apiData = [];
if (!empty($controllers) && count($controllers) > 0) {
foreach ($controllers as $class) {
$classData = $this->parseController($class);
if ($classData !== false) {
$apiData[] = $classData;
}
}
}
$json = array(
"data" => $apiData,
"tags" => $this->tags,
"groups" => $this->groups
);
return $json;
}
/**
* 获取生成文档的控制器列表
* @param string $path
* @return array
*/
protected function getConfigControllers(string $path): array
{
$config = $this->config;
$controllers = [];
$configControllers = $config['controllers'];
if (!empty($configControllers) && count($configControllers) > 0) {
foreach ($configControllers as $item) {
if ( strpos($item, $path) !== false && class_exists($item)) {
$controllers[] = $item;
}
}
}
return $controllers;
}
/**
* 获取目录下的控制器列表
* @param string $path
* @return array
*/
protected function getDirControllers(string $path): array
{
if ($path) {
if (strpos(App::getRootPath(), '/') !== false) {
$pathStr = str_replace("\\", "/", $path);
} else {
$pathStr = $path;
}
$dir = App::getRootPath() . $pathStr;
} else {
$dir = App::getRootPath() . $this->controller_layer;
}
$controllers = [];
if (is_dir($dir)) {
$controllers = $this->scanDir($dir, $path);
}
return $controllers;
}
/**
* 处理指定目录下的控制器
* @param string $dir
* @param string $appPath
* @return array
*/
protected function scanDir(string $dir, string $appPath): array
{
$list = [];
foreach (ClassMapGenerator::createMap($dir) as $class => $path) {
if (
!isset($this->config['filter_controllers']) ||
(isset($this->config['filter_controllers']) && !in_array($class, $this->config['filter_controllers'])) &&
$this->config['definitions'] != $class
) {
if (strpos($class, '\\') === false) {
$list[] = $appPath . "\\" . $class;
} else {
$list[] = $class;
}
}
}
return $list;
}
protected function parseController($class)
{
$config = $this->config;
$data = [];
$refClass = new ReflectionClass($class);
$classTextAnnotations = $this->parseTextAnnotation($refClass);
if (in_array("NotParse", $classTextAnnotations)) {
return false;
}
$title = $this->reader->getClassAnnotation($refClass, Title::class);
$group = $this->reader->getClassAnnotation($refClass, Group::class);
$sort = $this->reader->getClassAnnotation($refClass, Sort::class);
$routeGroup = $this->reader->getClassAnnotation($refClass, RouteGroup::class);
$controllersNameArr = explode("\\", $class);
$controllersName = $controllersNameArr[count($controllersNameArr) - 1];
$data['controller'] = $controllersName;
$data['group'] = !empty($group->value) ? $group->value : null;
$data['sort'] = !empty($sort->value) ? $sort->value : null;
if (!empty($data['group']) && !in_array($data['group'], $this->groups)) {
$this->groups[] = $data['group'];
}
$data['title'] = !empty($title) && !empty($title->value) ? $title->value : "";
if (empty($title)) {
if (!empty($classTextAnnotations) && count($classTextAnnotations) > 0) {
$data['title'] = $classTextAnnotations[0];
} else {
$data['title'] = $controllersName;
}
}
$methodList = [];
$filter_method = !empty($config['filter_method']) ? $config['filter_method'] : [];
$data['menu_key'] = $data['controller'] . "_" . mt_rand(10000, 99999);
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
if (!empty($refMethod->name) && !in_array($refMethod->name, $filter_method)) {
$methodItem = $this->parseAnnotation($refMethod, true,"controller");
if (!count((array)$methodItem)) {
continue;
}
$textAnnotations = $this->parseTextAnnotation($refMethod);
// 标注不解析的方法
if (in_array("NotParse", $textAnnotations)) {
continue;
}
// 无标题,且有文本注释
if (empty($methodItem['title']) && !empty($textAnnotations) && count($textAnnotations) > 0) {
$methodItem['title'] = $textAnnotations[0];
}
// 添加统一headers请求头参数
if (!empty($config['headers']) && !in_array("NotHeaders", $textAnnotations)) {
if (!empty($methodItem['header'])) {
$methodItem['header'] = Utils::arrayMergeAndUnique("name", $config['headers'], $methodItem['header']);
} else {
$methodItem['header'] = $config['headers'];
}
}
// 添加统一params请求参数
if (!empty($config['parameters']) && !in_array("NotParameters", $textAnnotations)) {
if (!empty($methodItem['param'])) {
$methodItem['param'] = Utils::arrayMergeAndUnique("name", $config['parameters'], $methodItem['param']);
} else {
$methodItem['param'] = $config['parameters'];
}
}
// 添加responses统一响应体
if (
!empty($config['responses']) &&
!is_string($config['responses']) &&
!in_array("NotResponses", $textAnnotations)
) {
// 显示在响应体中
$returned = [];
$hasMian = false;
if (isset($config['responses']['data']) && !$config['responses']['show_responses']) {
$responsesData = [];
} else if (isset($config['responses']['data']) && $config['responses']['show_responses'] === true) {
$responsesData = $config['responses']['data'];
} else {
$responsesData = $config['responses'];
}
// 合并统一响应体及响应参数相同的字段
$returnData = [];
$resKeys = [];
foreach ($responsesData as $resItem) {
$resKeys[]=$resItem['name'];
}
foreach ($methodItem['return'] as $returnItem){
if (!(in_array($returnItem['name'],$resKeys) && $returnItem['source']==='controller' && !empty($returnItem['replaceGlobal']))){
$returnData[]=$returnItem;
}
}
foreach ($responsesData as $resItem) {
$resData = $resItem;
$globalFind = Utils::getArrayFind($methodItem['return'],function ($item)use ($resItem){
if ($item['name'] == $resItem['name'] && $item['source']==='controller' && !empty($item['replaceGlobal'])){
return true;
}
return false;
});
if (!empty($globalFind)){
$resData = array_merge($resItem,$globalFind);
}else if (!empty($resData['main']) && $resData['main'] === true) {
$resData['params'] = $returnData;
$resData['resKeys']=$resKeys;
$hasMian = true;
}
$resData['find'] =$globalFind;
$returned[] = $resData;
}
if (!$hasMian) {
$returned = Utils::arrayMergeAndUnique("name", $returned, $methodItem['return']);
}
$methodItem['return'] = $returned;
}
// 默认method
if (empty($methodItem['method'])) {
$methodItem['method'] = !empty($config['default_method']) ? $config['default_method'] : 'GET';
}
// 默认default_author
if (empty($methodItem['author']) && !empty($config['default_author']) && !in_array("NotDefaultAuthor", $textAnnotations)) {
$methodItem['author'] = $config['default_author'];
}
// Tags
if (!empty($methodItem['tag'])) {
if (strpos($methodItem['tag'], ' ') !== false) {
$tagArr = explode(" ", $methodItem['tag']);
foreach ($tagArr as $tag) {
if (!in_array($tag, $this->tags)) {
$this->tags[] = $tag;
}
}
} else if (!in_array($methodItem['tag'], $this->tags)) {
$this->tags[] = $methodItem['tag'];
}
}
// 无url,自动生成
if (empty($methodItem['url'])) {
$methodItem['url'] = $this->autoCreateUrl($refMethod);
} else if (!empty($routeGroup->value)) {
// 路由分组url加上分组
$methodItem['url'] = '/' . $routeGroup->value . '/' . $methodItem['url'];
}
if (!empty($methodItem['url']) && substr($methodItem['url'], 0, 1) != "/") {
$methodItem['url'] = "/" . $methodItem['url'];
}
$methodItem['name'] = $refMethod->name;
$methodItem['menu_key'] = $methodItem['method'] . "_" . $refMethod->name . "_" . mt_rand(10000, 99999);
$methodList[] = $methodItem;
}
}
$data['children'] = $methodList;
return $data;
}
/**
* 自动生成url
* @param $method
* @return string
*/
protected function autoCreateUrl($method): string
{
if (Config::get("controller_auto_search") || !empty($this->config['controller_auto_search'])){
$pathArr = explode("\\", $method->class );
}else{
$searchString = $this->controller_layer . '\\';
$substr = substr(strstr($method->class, $searchString), strlen($searchString));
$pathArr = explode("\\", str_replace($substr, str_replace('\\', '.', $substr), $method->class));
}
// 控制器地址
$filterPathNames = array(App::getNamespace(), $this->controller_layer);
$classPathArr = [];
foreach ($pathArr as $item) {
if (!in_array($item, $filterPathNames)) {
if (!empty($this->config['auto_url_rule'])){
switch ($this->config['auto_url_rule']) {
case 'lcfirst':
$classPathArr[] = lcfirst($item);
break;
case 'ucfirst':
$classPathArr[] = ucfirst($item);
break;
default:
$classPathArr[] = $item;
}
}else{
$classPathArr[] = $item;
}
}
}
$classPath = implode('/', $classPathArr);
return '/' . $classPath . '/' . $method->name;
}
/**
* ref引用
* @param $refPath
* @param bool $enableRefService
* @return false|string[]
*/
protected function renderRef(string $refPath, bool $enableRefService = true): array
{
$res = ['type' => 'model'];
// 通用定义引入
if (strpos($refPath, '\\') === false) {
$config = $this->config;
$refPath = $config['definitions'] . '\\' . $refPath;
$data = $this->renderService($refPath);
$res['type'] = "service";
$res['data'] = $data;
return $res;
}
// 模型引入
$modelData = (new ParseModel($this->reader))->renderModel($refPath);
if ($modelData !== false) {
$res['data'] = $modelData;
return $res;
}
if ($enableRefService === false) {
return false;
}
$data = $this->renderService($refPath);
$res['type'] = "service";
$res['data'] = $data;
return $res;
}
/**
* 解析注释引用
* @param $refPath
* @return array
* @throws \ReflectionException
*/
protected function renderService(string $refPath)
{
$pathArr = explode("\\", $refPath);
$methodName = $pathArr[count($pathArr) - 1];
unset($pathArr[count($pathArr) - 1]);
$classPath = implode("\\", $pathArr);
$classReflect = new \ReflectionClass($classPath);
$methodName = trim($methodName);
$refMethod = $classReflect->getMethod($methodName);
$res = $this->parseAnnotation($refMethod, true);
return $res;
}
/**
* 处理Param/Returned的字段名name、params子级参数
* @param $values
* @return array
*/
protected function handleParamValue($values, string $field = 'param'): array
{
$name = "";
$params = [];
if (!empty($values) && is_array($values) && count($values) > 0) {
foreach ($values as $item) {
if (is_string($item)) {
$name = $item;
} else if (is_object($item)) {
if (!empty($item->ref)) {
$refRes = $this->renderRef($item->ref, true);
$params = $this->handleRefData($params, $refRes, $item, $field);
} else {
$param = [
"name" => "",
"type" => $item->type,
"desc" => $item->desc,
"default" => $item->default,
"require" => $item->require,
"childrenType"=> $item->childrenType
];
$children = $this->handleParamValue($item->value);
$param['name'] = $children['name'];
if (count($children['params']) > 0) {
$param['params'] = $children['params'];
}
$params[] = $param;
}
}
}
} else {
$name = $values;
}
return ['name' => $name, 'params' => $params];
}
/**
* 解析方法注释
* @param $refMethod
* @param bool $enableRefService 是否终止service的引入
* @param string $source 注解来源
* @return array
*/
protected function parseAnnotation($refMethod, bool $enableRefService = true,$source=""): array
{
$data = [];
if ($annotations = $this->reader->getMethodAnnotations($refMethod)) {
$headers = [];
$params = [];
$returns = [];
foreach ($annotations as $annotation) {
switch (true) {
case $annotation instanceof Param:
$params = $this->handleParamAndReturned($params,$annotation,'param',$enableRefService);
break;
case $annotation instanceof Returned:
$returns = $this->handleParamAndReturned($returns,$annotation,'return',$enableRefService,$source);
break;
case $annotation instanceof Header:
if (!empty($annotation->ref)) {
$refRes = $this->renderRef($annotation->ref, $enableRefService);
$headers = $this->handleRefData($headers, $refRes, $annotation, 'header');
} else {
$param = [
"name" => $annotation->value,
"desc" => $annotation->desc,
"require" => $annotation->require,
"type" => $annotation->type,
"default" => $annotation->default,
];
$headers[] = $param;
}
break;
case $annotation instanceof Route:
if (empty($data['method'])) {
$data['method'] = $annotation->method;
}
if (empty($data['url'])) {
$data['url'] = $annotation->value;
}
break;
case $annotation instanceof Author:
$data['author'] = $annotation->value;
break;
case $annotation instanceof Title:
$data['title'] = $annotation->value;
break;
case $annotation instanceof Desc:
$data['desc'] = $annotation->value;
break;
case $annotation instanceof ParamType:
$data['paramType'] = $annotation->value;
break;
case $annotation instanceof Url:
$data['url'] = $annotation->value;
break;
case $annotation instanceof Method:
$data['method'] = $annotation->value;
break;
case $annotation instanceof Tag:
$data['tag'] = $annotation->value;
break;
}
}
if ($headers && count($headers) > 0) {
$data['header'] = $headers;
}
$data['param'] = $params;
$data['return'] = $returns;
}
return $data;
}
/**
* 处理请求参数与返回参数
* @param $params
* @param $annotation
* @param string $type
* @param false $enableRefService
* @param string $source 注解来源
* @return array
*/
protected function handleParamAndReturned($params,$annotation,$type="param",$enableRefService=false,$source=""){
if (!empty($annotation->ref)) {
$refRes = $this->renderRef($annotation->ref, $enableRefService);
$params = $this->handleRefData($params, $refRes, $annotation, $type,$source);
} else {
$param = [
"name" => "",
"type" => $annotation->type,
"desc" => $annotation->desc,
"default" => $annotation->default,
"require" => $annotation->require,
"childrenType" => $annotation->childrenType,
"source" => $source,
"replaceGlobal" =>!empty($annotation->replaceGlobal)?$annotation->replaceGlobal:""
];
$children = $this->handleParamValue($annotation->value, $type);
$param['name'] = $children['name'];
if (count($children['params']) > 0) {
$param['params'] = $children['params'];
if ($annotation->type === 'tree' && !empty($annotation->childrenField)) {
// 类型为tree的
$param['params'][] = [
'params' => $children['params'],
'name' => $annotation->childrenField,
'type' => 'array',
'desc' => $annotation->childrenDesc,
];
}
}
// 合并同级已有的字段
$params = Utils::arrayMergeAndUnique("name", $params, [$param]);
}
return $params;
}
/**
* 解析非注解文本注释
* @param $refMethod
* @return array|false
*/
protected function parseTextAnnotation($refMethod): array
{
$annotation = $refMethod->getDocComment();
if (empty($annotation)) {
return [];
}
if (preg_match('#^/\*\*(.*)\*/#s', $annotation, $comment) === false)
return [];
$comment = trim($comment [1]);
if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false)
return [];
$data = [];
foreach ($lines[1] as $line) {
$line = trim($line);
if (!empty ($line) && strpos($line, '@') !== 0) {
$data[] = $line;
}
}
return $data;
}
/**
* 处理param、returned 参数
* @param $params
* @param $refRes
* @param $annotation
* @param string|null $source 注解来源
* @return array
*/
protected function handleRefData($params, $refRes, $annotation, string $field,$source=""): array
{
if ($refRes['type'] === "model" && count($refRes['data']) > 0) {
// 模型引入
$data = $refRes['data'];
} else if ($refRes['type'] === "service" && !empty($refRes['data']) && !empty($refRes['data'][$field])) {
// service引入
$data = $refRes['data'][$field];
} else {
return $params;
}
// 过滤field
if (!empty($annotation->field)) {
$data = (new Utils())->filterParamsField($data, $annotation->field, 'field');
}
// 过滤withoutField
if (!empty($annotation->withoutField)) {
$data = (new Utils())->filterParamsField($data, $annotation->withoutField, 'withoutField');
}
if (!empty($annotation->value)) {
$item = [
'name' => $annotation->value,
'desc' => $annotation->desc,
'type' => $annotation->type,
'require' => $annotation->require,
'default' => $annotation->default,
'params' => $data,
'source'=>$source,
"replaceGlobal" =>!empty($annotation->replaceGlobal)?$annotation->replaceGlobal:""
];
$children = $this->handleParamValue($annotation->value, 'param');
$item['name'] = $children['name'];
if (count($children['params']) > 0) {
$item['params'] = Utils::arrayMergeAndUnique("name",$data,$children['params']);
if ($annotation->type === 'tree' && !empty($annotation->childrenField)) {
// 类型为tree的
$item['params'][] = [
'params' => $item['params'],
'name' => $annotation->childrenField,
'type' => 'array',
'desc' => $annotation->childrenDesc,
];
}
}
$params[] = $item;
} else {
$params = array_merge($params, $data);
}
return $params;
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc\parseApi;
use think\facade\App;
use hg\apidoc\Utils;
use think\facade\Config;
class ParseMarkdown
{
protected $config = [];
public function __construct()
{
$this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
}
/**
* 获取md文档菜单
* @return array
*/
public function getDocsMenu(): array
{
$config = $this->config;
$docData = [];
if (!empty($config['docs']) && !empty($config['docs']['menus']) && count($config['docs']['menus']) > 0) {
$docData = $this->handleDocsMenuData($config['docs']['menus']);
}
return $docData;
}
/**
* 处理md文档菜单数据
* @param array $menus
* @return array
*/
protected function handleDocsMenuData(array $menus): array
{
$list = [];
foreach ($menus as $item) {
if (!empty($item['items']) && count($item['items']) > 0) {
$item['items'] = $this->handleDocsMenuData($item['items']);
$item['group'] = 'markdown_doc';
$item['menu_key'] = "md_group_" . mt_rand(10000, 99999);
$list[] = $item;
} else {
$item['type'] = 'md';
$item['menu_key'] = "md_" . mt_rand(10000, 99999);
$list[] = $item;
}
}
return $list;
}
/**
* 获取md文档内容
* @param string $appKey
* @param string $path
* @return string
*/
public function getContent(string $appKey, string $path): string
{
$currentApps = (new Utils())->getCurrentApps($appKey);
$mdPath = (new Utils())->replaceCurrentAppTemplate($path, $currentApps);
$filePath = App::getRootPath() . $mdPath . '.md';
$contents = Utils::getFileContent($filePath);
return $contents;
}
}

View File

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