diff --git a/app/Http/Controllers/Api/apidoc.md b/app/Http/Controllers/Api/apidoc.md
new file mode 100644
index 000000000..bab99fcb4
--- /dev/null
+++ b/app/Http/Controllers/Api/apidoc.md
@@ -0,0 +1,378 @@
+# apiDoc 参数标签说明(完整速查)
+
+apiDoc 使用内联注释为 RESTful API 自动生成文档。
+以下为所有官方支持的参数与其说明。
+
+---
+
+## @api
+**定义 API 方法的基本信息**
+
+```js
+@api {method} path title
+```
+
+- **method**:请求方法,如 `GET`、`POST`、`PUT`、`DELETE` 等
+- **path**:请求路径,例如 `/user/:id`
+- **title**:简短标题(显示在文档中)
+
+📘 示例:
+```js
+@api {get} /user/:id Get user info
+```
+
+---
+
+## @apiBody
+**定义请求体参数**
+
+```js
+@apiBody [{type}] [field=defaultValue] [description]
+```
+
+- `{type}` 参数类型(如 String, Number, Object, String[])
+- `[field]` 可选字段(方括号表示可选)
+- `=defaultValue` 默认值
+- `description` 参数说明
+
+📘 示例:
+```js
+@apiBody {String} lastname Mandatory Lastname.
+@apiBody {Object} [address] Optional address object.
+@apiBody {String} [address[city]] Optional city.
+```
+
+---
+
+## @apiDefine
+**定义可复用的文档块**
+
+```js
+@apiDefine name [title] [description]
+```
+
+- `name`:唯一标识
+- `title`:简短标题
+- `description`:多行描述
+
+📘 示例:
+```js
+@apiDefine MyError
+@apiError UserNotFound The id of the User was not found.
+```
+
+---
+
+## @apiDeprecated
+**标记接口为弃用状态**
+
+```js
+@apiDeprecated [text]
+```
+
+- `text`:提示文本,可带链接到新方法
+
+📘 示例:
+```js
+@apiDeprecated use now (#User:GetDetails)
+```
+
+---
+
+## @apiDescription
+**描述接口详细说明**
+
+```js
+@apiDescription text
+```
+
+📘 示例:
+```js
+@apiDescription This is the Description.
+It is multiline capable.
+```
+
+---
+
+## @apiError
+**定义错误返回参数**
+
+```js
+@apiError [(group)] [{type}] field [description]
+```
+
+📘 示例:
+```js
+@apiError UserNotFound The id of the User was not found.
+```
+
+---
+
+## @apiErrorExample
+**定义错误返回示例**
+
+```js
+@apiErrorExample [{type}] [title]
+example
+```
+
+📘 示例:
+```js
+@apiErrorExample {json} Error-Response:
+ HTTP/1.1 404 Not Found
+ { "error": "UserNotFound" }
+```
+
+---
+
+## @apiExample
+**定义接口使用示例**
+
+```js
+@apiExample [{type}] title
+example
+```
+
+📘 示例:
+```js
+@apiExample {curl} Example usage:
+ curl -i http://localhost/user/4711
+```
+
+---
+
+## @apiGroup
+**定义所属分组**
+
+```js
+@apiGroup name
+```
+
+📘 示例:
+```js
+@apiGroup User
+```
+
+---
+
+## @apiHeader
+**定义请求头参数**
+
+```js
+@apiHeader [(group)] [{type}] [field=defaultValue] [description]
+```
+
+📘 示例:
+```js
+@apiHeader {String} access-key Users unique access-key.
+```
+
+---
+
+## @apiHeaderExample
+**定义请求头示例**
+
+```js
+@apiHeaderExample [{type}] [title]
+example
+```
+
+📘 示例:
+```js
+@apiHeaderExample {json} Header-Example:
+ {
+ "Accept-Encoding": "gzip, deflate"
+ }
+```
+
+---
+
+## @apiIgnore
+**忽略当前文档块**
+
+```js
+@apiIgnore [hint]
+```
+
+📘 示例:
+```js
+@apiIgnore Not finished method
+```
+
+---
+
+## @apiName
+**定义接口唯一名称**
+
+```js
+@apiName name
+```
+
+📘 示例:
+```js
+@apiName GetUser
+```
+
+---
+
+## @apiParam
+**定义请求参数**
+
+```js
+@apiParam [(group)] [{type}] [field=defaultValue] [description]
+```
+
+📘 示例:
+```js
+@apiParam {Number} id Users unique ID.
+@apiParam {String} [firstname] Optional firstname.
+@apiParam {String} country="DE" Mandatory with default.
+```
+
+---
+
+## @apiParamExample
+**定义参数请求示例**
+
+```js
+@apiParamExample [{type}] [title]
+example
+```
+
+📘 示例:
+```js
+@apiParamExample {json} Request-Example:
+ { "id": 4711 }
+```
+
+---
+
+## @apiPermission
+**定义权限要求**
+
+```js
+@apiPermission name
+```
+
+📘 示例:
+```js
+@apiPermission admin
+```
+
+---
+
+## @apiPrivate
+**标记接口为私有(可过滤)**
+
+```js
+@apiPrivate
+```
+
+---
+
+## @apiQuery
+**定义查询参数(?query)**
+
+```js
+@apiQuery [{type}] [field=defaultValue] [description]
+```
+
+📘 示例:
+```js
+@apiQuery {Number} id Users unique ID.
+@apiQuery {String} [sort="asc"] Sort order.
+```
+
+---
+
+## @apiSampleRequest
+**定义接口测试请求 URL**
+
+```js
+@apiSampleRequest url
+```
+
+📘 示例:
+```js
+@apiSampleRequest http://test.github.com/some_path/
+```
+
+---
+
+## @apiSuccess
+**定义成功返回参数**
+
+```js
+@apiSuccess [(group)] [{type}] field [description]
+```
+
+📘 示例:
+```js
+@apiSuccess {String} firstname Firstname of the User.
+@apiSuccess {String} lastname Lastname of the User.
+```
+
+---
+
+## @apiSuccessExample
+**定义成功返回示例**
+
+```js
+@apiSuccessExample [{type}] [title]
+example
+```
+
+📘 示例:
+```js
+@apiSuccessExample {json} Success-Response:
+ HTTP/1.1 200 OK
+ { "firstname": "John", "lastname": "Doe" }
+```
+
+---
+
+## @apiUse
+**引用定义块(@apiDefine)**
+
+```js
+@apiUse name
+```
+
+📘 示例:
+```js
+@apiDefine MySuccess
+@apiSuccess {String} firstname User firstname.
+
+@apiUse MySuccess
+```
+
+---
+
+## @apiVersion
+**定义接口版本**
+
+```js
+@apiVersion version
+```
+
+📘 示例:
+```js
+@apiVersion 1.6.2
+```
+
+---
+
+# 附录:常用标签速查表
+
+| 标签 | 作用 | 示例 |
+|------|------|------|
+| `@api` | 定义接口 | `@api {get} /user/:id` |
+| `@apiName` | 唯一名称 | `@apiName GetUser` |
+| `@apiGroup` | 所属分组 | `@apiGroup User` |
+| `@apiParam` | 请求参数 | `@apiParam {Number} id Users unique ID.` |
+| `@apiBody` | 请求体参数 | `@apiBody {String} name Username.` |
+| `@apiQuery` | 查询参数 | `@apiQuery {String} keyword Search term.` |
+| `@apiHeader` | Header 参数 | `@apiHeader {String} token Auth token.` |
+| `@apiSuccess` | 成功返回字段 | `@apiSuccess {String} name Username.` |
+| `@apiError` | 错误返回字段 | `@apiError NotFound User not found.` |
+| `@apiVersion` | 版本号 | `@apiVersion 1.0.0` |
diff --git a/app/Http/Controllers/Api/apidoc.php b/app/Http/Controllers/Api/apidoc.php
index f0ed9ab97..82007855d 100755
--- a/app/Http/Controllers/Api/apidoc.php
+++ b/app/Http/Controllers/Api/apidoc.php
@@ -1,89 +1,137 @@
$text) {
- if (in_array(strtolower($matchs[1][$key]), array('get', 'post'))) {
- $expl = explode(" ", __sRemove($text));
- $end = $expl[1];
- if ($expl[2]) {
- $end = '';
- foreach ($expl AS $k=>$v) { if ($k >= 2) { $end.= " ".$v; } }
- }
- $newtext = "* @api {".$matchs[1][$key]."} ".$expl[0]." ".__zeroFill($i, 2).". ".trim($end);
- $content = str_replace("* @api {".$matchs[1][$key]."} ".$text, $newtext, $content);
- $i++;
- //
- echo $newtext;
- echo "\r\n";
- }
- }
- if ($i > 1) {
- file_put_contents($fillPath, $content);
- }
- }
-}
-echo "Success \n";
+const NUMBER_WIDTH = 2;
-/** ************************************************************** */
-/** ************************************************************** */
-/** ************************************************************** */
+$isRestore = isset($argv[1]) && strtolower($argv[1]) === 'restore';
-/**
- * 替换所有空格
- * @param $str
- * @return mixed
- */
-function __sRemove($str) {
- $str = str_replace(" ", " ", $str);
- if (__strExists($str, " ")) {
- return __sRemove($str);
- }
- return $str;
+$basePath = dirname(__FILE__) . '/';
+$controllerFiles = glob($basePath . '*Controller.php');
+
+if (!$controllerFiles) {
+ echo "No Controller.php files found\n";
+ exit(0);
}
+foreach ($controllerFiles as $filePath) {
+ $original = file_get_contents($filePath);
+ [$updated, $linesChanged] = processFile($original, $isRestore);
+
+ if (count($linesChanged) === 0) {
+ continue;
+ }
+
+ file_put_contents($filePath, $updated);
+
+ foreach ($linesChanged as $line) {
+ echo $line . "\n";
+ }
+}
+
+echo $isRestore ? "Restore Success \n" : "Success \n";
+
/**
- * 是否包含字符
- * @param $string
- * @param $find
- * @return bool
+ * 处理单个文件内容
+ *
+ * @param string $content
+ * @param bool $restore
+ * @return array{string, array}
*/
-function __strExists($string, $find)
+function processFile(string $content, bool $restore): array
{
- return str_contains($string, $find);
+ $lineChanges = [];
+ $counter = 1;
+
+ $pattern = '/\* @api \{([^\}]+)\}\s+([^\s]+)([^\r\n]*)(\r?\n)/';
+
+ $updated = preg_replace_callback(
+ $pattern,
+ function (array $matches) use ($restore, &$counter, &$lineChanges) {
+ $method = trim($matches[1]);
+ if (!in_array(strtolower($method), ['get', 'post'], true)) {
+ return $matches[0];
+ }
+
+ $endpoint = trim($matches[2]);
+ $suffix = normalizeDescription(stripExistingNumbering($matches[3]));
+
+ if (!$restore) {
+ $numberedSuffix = formatNumber($counter) . '.';
+ if ($suffix !== '') {
+ $numberedSuffix .= ' ' . $suffix;
+ }
+ $counter++;
+ } else {
+ $numberedSuffix = $suffix;
+ }
+
+ $newLine = renderAnnotation($method, $endpoint, $numberedSuffix);
+
+ if ($newLine !== rtrim($matches[0], "\r\n")) {
+ $lineChanges[] = $newLine;
+ }
+
+ return $newLine . $matches[4];
+ },
+ $content
+ );
+
+ if ($updated === null) {
+ return [$content, []];
+ }
+
+ return [$updated, $lineChanges];
}
/**
- * @param string $str 补零
- * @param int $length
- * @param int $after
- * @return bool|string
+ * 生成格式化后的注释行
*/
-function __zeroFill($str, $length = 0, $after = 1) {
- if (strlen($str) >= $length) {
- return $str;
+function renderAnnotation(string $method, string $endpoint, string $suffix = ''): string
+{
+ $line = "* @api {" . $method . "} " . $endpoint;
+
+ if ($suffix !== '') {
+ if ($suffix[0] !== ' ') {
+ $line .= ' ';
+ }
+ $line .= $suffix;
}
- $_str = '';
- for ($i = 0; $i < $length; $i++) {
- $_str .= '0';
- }
- if ($after) {
- $_ret = substr($_str . $str, $length * -1);
- } else {
- $_ret = substr($str . $_str, 0, $length);
- }
- return $_ret;
+
+ return $line;
+}
+
+/**
+ * 移除已有编号部分
+ */
+function stripExistingNumbering(string $text): string
+{
+ $trimmed = ltrim($text);
+ $pattern = '/^\d+\.\s*/';
+ return preg_replace($pattern, '', $trimmed) ?? $trimmed;
+}
+
+/**
+ * 压缩多余空格
+ */
+function normalizeDescription(string $text): string
+{
+ $text = trim($text);
+ if ($text === '') {
+ return '';
+ }
+
+ return preg_replace('/\s+/', ' ', $text) ?? $text;
+}
+
+/**
+ * 生成固定宽度的数字
+ */
+function formatNumber(int $number): string
+{
+ return str_pad((string) $number, NUMBER_WIDTH, '0', STR_PAD_LEFT);
}
diff --git a/cmd b/cmd
index c5fbe0a06..d771a0270 100755
--- a/cmd
+++ b/cmd
@@ -805,6 +805,7 @@ case "$1" in
shift 1
container_exec php "php app/Http/Controllers/Api/apidoc.php"
docker run -it --rm -v ${WORK_DIR}:/home/node/apidoc kuaifan/apidoc -i app/Http/Controllers/Api -o public/docs
+ container_exec php "php app/Http/Controllers/Api/apidoc.php restore"
;;
"debug")
shift 1