diff --git a/Gemfile b/Gemfile index a4c7c2414..3da6f273d 100644 --- a/Gemfile +++ b/Gemfile @@ -135,4 +135,4 @@ gem 'doorkeeper' gem 'doorkeeper-jwt' -gem 'gitea-client', '~> 0.10.1' \ No newline at end of file +gem 'gitea-client', '~> 0.10.2' \ No newline at end of file diff --git a/app/controllers/api/v1/projects_controller.rb b/app/controllers/api/v1/projects_controller.rb index 0873213de..bc51362ae 100644 --- a/app/controllers/api/v1/projects_controller.rb +++ b/app/controllers/api/v1/projects_controller.rb @@ -1,5 +1,5 @@ class Api::V1::ProjectsController < Api::V1::BaseController - before_action :require_public_and_member_above, only: [:show, :compare] + before_action :require_public_and_member_above, only: [:show, :compare, :blame] def index render_ok @@ -12,4 +12,9 @@ class Api::V1::ProjectsController < Api::V1::BaseController def compare @result_object = Api::V1::Projects::CompareService.call(@project, params[:from], params[:to], current_user&.gitea_token) end + + def blame + @result_object = Api::V1::Projects::BlameService.call(@project, params[:sha], params[:filepath], current_user&.gitea_token) + puts @result_object + end end \ No newline at end of file diff --git a/app/docs/slate/source/includes/_repositories.md b/app/docs/slate/source/includes/_repositories.md index c760fc624..5b5067495 100644 --- a/app/docs/slate/source/includes/_repositories.md +++ b/app/docs/slate/source/includes/_repositories.md @@ -1614,6 +1614,203 @@ await octokit.request('GET /api/v1/yystopf/csfjkkj/commits/80dd40214a58622312393 Success Data. +## 获取单个文件的blame信息 +根据分支、标签、commitID获取某个文件的blame信息 + +> 示例: + +```shell +curl -X GET \ +-d "sha=master" \ +-d "filepath=hd.txt" \ +http://localhost:3000/api/v1/yystopf/csfjkkj/blame.json +``` + +```javascript +await octokit.request('GET /api/v1/yystopf/csfjkkj/blame.json') +``` + +### HTTP 请求 +`GET /api/v1/:owner/:repo/blame.json` + +### 请求参数: +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| | string |用户登录名 | +|repo |是| | string |项目标识identifier | +|sha |是| | string |分支、标签或提交记录id | +|filepath|是| | string |文件路径| +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|file_size|int|文件大小| +|file_name|string|文件名称| +|num_lines|int|文件总行数| +|blame_parts.commit|object|提交| +|blame_parts.current_number|int|当前行数| +|blame_parts.effect_line|int|影响的行数| +|blame_parts.lines|array|行内容| + +> 返回的JSON示例: + +```json +{ + "file_size": 32, + "file_name": "hd.txt", + "num_lines": 12, + "blame_parts": [ + { + "commit": { + "sha": "40f76e80bf5bc41fcc94c28ca8a6eab506c15215", + "author": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "committer": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "commit_message": "fix\n", + "authored_time": "2022-07-04 18:41:25", + "committed_time": "2022-07-04 18:41:25", + "created_time": "2022-07-04 18:41:25" + }, + "current_number": 1, + "effect_line": 5, + "lines": [ + "dkfj", + "s", + "324", + "234", + "2" + ] + }, + { + "commit": { + "sha": "86c62a1e91c07b58b8aa6c89b94856d89c0f7e55", + "author": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "committer": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "commit_message": "fix\n", + "authored_time": "2022-07-05 11:00:45", + "committed_time": "2022-07-05 11:00:45", + "created_time": "2022-07-05 11:00:45" + }, + "current_number": 6, + "effect_line": 1, + "lines": [ + "dd" + ] + }, + { + "commit": { + "sha": "40f76e80bf5bc41fcc94c28ca8a6eab506c15215", + "author": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "committer": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "commit_message": "fix\n", + "authored_time": "2022-07-04 18:41:25", + "committed_time": "2022-07-04 18:41:25", + "created_time": "2022-07-04 18:41:25" + }, + "current_number": 7, + "effect_line": 3, + "lines": [ + "23", + "4", + "23" + ] + }, + { + "commit": { + "sha": "86c62a1e91c07b58b8aa6c89b94856d89c0f7e55", + "author": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "committer": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "commit_message": "fix\n", + "authored_time": "2022-07-05 11:00:45", + "committed_time": "2022-07-05 11:00:45", + "created_time": "2022-07-05 11:00:45" + }, + "current_number": 10, + "effect_line": 1, + "lines": [ + "s1" + ] + }, + { + "commit": { + "sha": "40f76e80bf5bc41fcc94c28ca8a6eab506c15215", + "author": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "committer": { + "id": null, + "login": "viletyy", + "name": "viletyy", + "type": null, + "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png" + }, + "commit_message": "fix\n", + "authored_time": "2022-07-04 18:41:25", + "committed_time": "2022-07-04 18:41:25", + "created_time": "2022-07-04 18:41:25" + }, + "current_number": 11, + "effect_line": 1, + "lines": [ + "" + ] + } + ] +} +``` + ## 获取比较提交blame 根据分支名、标签、commit ID来获取代码对比blame diff --git a/app/services/api/v1/projects/blame_service.rb b/app/services/api/v1/projects/blame_service.rb new file mode 100644 index 000000000..d419fec14 --- /dev/null +++ b/app/services/api/v1/projects/blame_service.rb @@ -0,0 +1,38 @@ +class Api::V1::Projects::BlameService < ApplicationService + include ActiveModel::Model + + attr_reader :project, :sha, :filepath, :owner, :repo, :token + attr_accessor :gitea_data + + validates :sha, :filepath, presence: true + + def initialize(project, sha, filepath, token=nil) + @project = project + @owner = project&.owner.login + @repo = project&.identifier + @sha = sha + @filepath = filepath + @token = token + end + + def call + raise Error, errors.full_messages.join(",") unless valid? + load_gitea_data + + gitea_data + end + + private + def request_params + { + access_token: token, + sha: sha, + filepath: filepath + } + end + + def load_gitea_data + @gitea_data = $gitea_client.get_repos_blame_by_owner_repo(owner, repo, {query: request_params}) + raise Error, '获取项目blame失败!' unless @gitea_data.is_a?(Hash) + end +end \ No newline at end of file diff --git a/app/views/api/v1/projects/blame.json.jbuilder b/app/views/api/v1/projects/blame.json.jbuilder new file mode 100644 index 000000000..d2e689537 --- /dev/null +++ b/app/views/api/v1/projects/blame.json.jbuilder @@ -0,0 +1,22 @@ +json.file_size @result_object['file_size'] +json.file_name @result_object['file_name'] +json.num_lines @result_object['num_lines'] +json.blame_parts @result_object['blame_parts'] do |part| + json.commit do + json.sha part['commit']['id'] + json.author do + json.partial! 'api/v1/users/commit_user', locals: { user: render_cache_commit_author(part['commit']['author']), name: part['commit']['author']['Name'] } + end + + json.committer do + json.partial! 'api/v1/users/commit_user', locals: { user: render_cache_commit_author(part['commit']['commiter']), name: part['commit']['commiter']['Name'] } + end + json.commit_message part['commit']['commit_message'] + json.authored_time part['commit']['authored_time'].to_time.strftime("%Y-%m-%d %H:%M:%S") + json.committed_time part['commit']['committed_time'].to_time.strftime("%Y-%m-%d %H:%M:%S") + json.created_time part['commit']['created_time'].to_time.strftime("%Y-%m-%d %H:%M:%S") + end + json.current_number part['current_number'] + json.effect_line part['effect_line'] + json.lines part['lines'] +end \ No newline at end of file diff --git a/config/routes/api.rb b/config/routes/api.rb index 9c71ea800..6a01d1aba 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -12,6 +12,7 @@ defaults format: :json do resource :projects, path: '/', only: [:show, :update, :edit, :destroy] do collection do get :compare + get :blame end end diff --git a/public/docs/api.html b/public/docs/api.html index f3e56574f..0fcb033ae 100644 --- a/public/docs/api.html +++ b/public/docs/api.html @@ -560,7 +560,10 @@ 获取单个提交的blame信息
根据分支、标签、commitID获取某个文件的blame信息
+ +++示例:
+
curl -X GET \
+-d "sha=master" \
+-d "filepath=hd.txt" \
+http://localhost:3000/api/v1/yystopf/csfjkkj/blame.json
+
await octokit.request('GET /api/v1/yystopf/csfjkkj/blame.json')
+
GET /api/v1/:owner/:repo/blame.json
参数 | +必选 | +默认 | +类型 | +字段说明 | +
---|---|---|---|---|
owner | +是 | ++ | string | +用户登录名 | +
repo | +是 | ++ | string | +项目标识identifier | +
sha | +是 | ++ | string | +分支、标签或提交记录id | +
filepath | +是 | ++ | string | +文件路径 | +
参数 | +类型 | +字段说明 | +
---|---|---|
file_size | +int | +文件大小 | +
file_name | +string | +文件名称 | +
num_lines | +int | +文件总行数 | +
blame_parts.commit | +object | +提交 | +
blame_parts.current_number | +int | +当前行数 | +
blame_parts.effect_line | +int | +影响的行数 | +
blame_parts.lines | +array | +行内容 | +
++返回的JSON示例:
+
{
+ "file_size": 32,
+ "file_name": "hd.txt",
+ "num_lines": 12,
+ "blame_parts": [
+ {
+ "commit": {
+ "sha": "40f76e80bf5bc41fcc94c28ca8a6eab506c15215",
+ "author": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "committer": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "commit_message": "fix\n",
+ "authored_time": "2022-07-04 18:41:25",
+ "committed_time": "2022-07-04 18:41:25",
+ "created_time": "2022-07-04 18:41:25"
+ },
+ "current_number": 1,
+ "effect_line": 5,
+ "lines": [
+ "dkfj",
+ "s",
+ "324",
+ "234",
+ "2"
+ ]
+ },
+ {
+ "commit": {
+ "sha": "86c62a1e91c07b58b8aa6c89b94856d89c0f7e55",
+ "author": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "committer": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "commit_message": "fix\n",
+ "authored_time": "2022-07-05 11:00:45",
+ "committed_time": "2022-07-05 11:00:45",
+ "created_time": "2022-07-05 11:00:45"
+ },
+ "current_number": 6,
+ "effect_line": 1,
+ "lines": [
+ "dd"
+ ]
+ },
+ {
+ "commit": {
+ "sha": "40f76e80bf5bc41fcc94c28ca8a6eab506c15215",
+ "author": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "committer": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "commit_message": "fix\n",
+ "authored_time": "2022-07-04 18:41:25",
+ "committed_time": "2022-07-04 18:41:25",
+ "created_time": "2022-07-04 18:41:25"
+ },
+ "current_number": 7,
+ "effect_line": 3,
+ "lines": [
+ "23",
+ "4",
+ "23"
+ ]
+ },
+ {
+ "commit": {
+ "sha": "86c62a1e91c07b58b8aa6c89b94856d89c0f7e55",
+ "author": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "committer": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "commit_message": "fix\n",
+ "authored_time": "2022-07-05 11:00:45",
+ "committed_time": "2022-07-05 11:00:45",
+ "created_time": "2022-07-05 11:00:45"
+ },
+ "current_number": 10,
+ "effect_line": 1,
+ "lines": [
+ "s1"
+ ]
+ },
+ {
+ "commit": {
+ "sha": "40f76e80bf5bc41fcc94c28ca8a6eab506c15215",
+ "author": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "committer": {
+ "id": null,
+ "login": "viletyy",
+ "name": "viletyy",
+ "type": null,
+ "image_url": "system/lets/letter_avatars/2/V/39_141_222/120.png"
+ },
+ "commit_message": "fix\n",
+ "authored_time": "2022-07-04 18:41:25",
+ "committed_time": "2022-07-04 18:41:25",
+ "created_time": "2022-07-04 18:41:25"
+ },
+ "current_number": 11,
+ "effect_line": 1,
+ "lines": [
+ ""
+ ]
+ }
+ ]
+}
+
根据分支名、标签、commit ID来获取代码对比blame
@@ -10095,9 +10355,9 @@ http://localhost:3000/api/v1/yystopf/csfjkkj/commits.json -d "to=master" \ http://localhost:3000/api/v1/yystopf/csfjkkj/compare.jsonawait octokit.request('GET /api/v1/yystopf/csfjkkj/compare.json') -
HTTP 请求
+HTTP 请求
-
GET /api/v1/:owner/:repo/compare.json
请求参数:
+请求参数:
-
参数 @@ -10136,7 +10396,7 @@ http://localhost:3000/api/v1/yystopf/csfjkkj/compare.json目标分支、标签、commitID 返回字段说明:
+返回字段说明:
参数 @@ -10421,9 +10681,9 @@ http://localhost:3000/api/v1/yystopf/csfjkkj/compare.jsoncurl -X GET \ http://localhost:3000/api/v1/yystopf/ceshi/webhooks.json
await octokit.request('GET /api/v1/yystopf/ceshi/webhooks.json') -
HTTP 请求
+HTTP 请求
-
GET /api/v1/:owner/:repo/webhooks.json
请求参数:
+请求参数:
-
参数 @@ -10448,7 +10708,7 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks.json项目标识identifier 返回字段说明:
+返回字段说明:
参数 @@ -10541,9 +10801,9 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks.jsoncurl -X GET \ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/3.json
await octokit.request('GET /api/v1/yystopf/ceshi/webhooks/3.json') -
HTTP 请求
+HTTP 请求
-
GET /api/v1/:owner/:repo/webhooks/:id.json
请求参数:
+请求参数:
-
参数 @@ -10575,7 +10835,7 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/3.jsonwebhook ID 返回字段说明:
+返回字段说明:
参数 @@ -10698,214 +10958,8 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/3.jsoncurl -X POST \ http://localhost:3000/api/v1/yystopf/ceshi/webhooks.json
await octokit.request('POST /api/v1/yystopf/ceshi/webhooks.json') -
HTTP 请求
--
POST /api/v1/:owner/:repo/webhooks.json
请求参数:
--
- -- - -参数 -必选 -默认 -类型 -字段说明 -- -owner -是 -- string -用户登录名 -- -repo -是 -- string -项目标识identifier -- -webhook.url -是 -- string -目标url -- -webhook.http_method -是 -- string -http方法, POST和GET -- -webhook.content_type -是 -- string -POST Content Type -- -webhook.secret -否 -- string -密钥文本 -- -webhook.active -是 -- bool -是否激活 -- -webhook.branch_filter -否 -- string -分支过滤 -- -webhook.events -否 -- array -触发事件 -触发事件字段说明
- --
- -- - -参数 -含义 -- -create -创建分支或标签 -- -delete -分支或标签删除 -- -push -git仓库推送 -- -pull_request -合并请求 -- -pull_request_assign -合并请求被指派 -- -pull_request_review_approved -合并请求被批准 -- -pull_request_review_rejected -合并请求被拒绝 ---请求的JSON示例:
-{ - "active": true, - "content_type": "json", - "http_method": "GET", - "secret": "123456", - "url": "http://localhost:10000", - "branch_filter": "*", - "events": ["push"] -} -
返回字段说明:
--
- -- - -参数 -类型 -字段说明 -- -id -int -id -- -url -string -地址 -- -content_type -string -POST Content Type -- -is_active -bool -是否激活 -- -type -string -类型 -- -events -array -触发事件 -- -create_time -string -创建时间 ---返回的JSON示例:
-- -{ - "id": 68, - "content_type": "json", - "http_method": "GET", - "url": "http://127.0.0.1:3000", - "events": [ - "create", - "delete", - "push", - "pull_request", - "pull_request_assign", - "pull_request_review_approved", - "pull_request_review_rejected" - ], - "active": true, - "branch_filter": "*", - "created_at": "2022-06-23 15:52" -} -
更新仓库webhook
-更新仓库webhook
- ---示例:
-curl -X PATCH \ -http://localhost:3000/api/v1/yystopf/ceshi/webhooks/7.json -
await octokit.request('PATCH /api/v1/yystopf/ceshi/webhooks/7.json')
HTTP 请求
-+
PATCH /api/v1/:owner/:repo/webhooks/68.json
POST /api/v1/:owner/:repo/webhooks.json
请求参数:
@@ -10931,13 +10985,6 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/7.json 项目标识identifier - -id -是 -- string -webhook id -webhook.url 是 @@ -11039,6 +11086,219 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/7.json "events": ["push"] } 返回字段说明:
++
+ ++ + +参数 +类型 +字段说明 ++ +id +int +id ++ +url +string +地址 ++ +content_type +string +POST Content Type ++ +is_active +bool +是否激活 ++ +type +string +类型 ++ +events +array +触发事件 ++ +create_time +string +创建时间 +++返回的JSON示例:
++ +{ + "id": 68, + "content_type": "json", + "http_method": "GET", + "url": "http://127.0.0.1:3000", + "events": [ + "create", + "delete", + "push", + "pull_request", + "pull_request_assign", + "pull_request_review_approved", + "pull_request_review_rejected" + ], + "active": true, + "branch_filter": "*", + "created_at": "2022-06-23 15:52" +} +
更新仓库webhook
+更新仓库webhook
+ +++示例:
+curl -X PATCH \ +http://localhost:3000/api/v1/yystopf/ceshi/webhooks/7.json +
await octokit.request('PATCH /api/v1/yystopf/ceshi/webhooks/7.json') +
HTTP 请求
++
PATCH /api/v1/:owner/:repo/webhooks/68.json
请求参数:
++
+ ++ + +参数 +必选 +默认 +类型 +字段说明 ++ +owner +是 ++ string +用户登录名 ++ +repo +是 ++ string +项目标识identifier ++ +id +是 ++ string +webhook id ++ +webhook.url +是 ++ string +目标url ++ +webhook.http_method +是 ++ string +http方法, POST和GET ++ +webhook.content_type +是 ++ string +POST Content Type ++ +webhook.secret +否 ++ string +密钥文本 ++ +webhook.active +是 ++ bool +是否激活 ++ +webhook.branch_filter +否 ++ string +分支过滤 ++ +webhook.events +否 ++ array +触发事件 +触发事件字段说明
+ ++
+ ++ + +参数 +含义 ++ +create +创建分支或标签 ++ +delete +分支或标签删除 ++ +push +git仓库推送 ++ +pull_request +合并请求 ++ +pull_request_assign +合并请求被指派 ++ +pull_request_review_approved +合并请求被批准 ++ +pull_request_review_rejected +合并请求被拒绝 +++请求的JSON示例:
+{ + "active": true, + "content_type": "json", + "http_method": "GET", + "secret": "123456", + "url": "http://localhost:10000", + "branch_filter": "*", + "events": ["push"] +} +
返回字段说明:
@@ -11073,9 +11333,9 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/7.json返回的JSON示例:
curl -X DELETE \ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/7.json
await octokit.request('DELETE /api/v1/yystopf/ceshi/webhooks/7.json') -
HTTP 请求
+HTTP 请求
-
DELETE /api/v1/:owner/:repo/webhooks/:id.json
请求参数:
+请求参数:
-
参数 @@ -11107,7 +11367,7 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/7.jsonwebhook id 返回字段说明:
+返回字段说明:
@@ -11128,9 +11388,9 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/7.json返回的JSON示例:
curl -X GET \ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/3/hooktasks.json
await octokit.request('GET /api/v1/yystopf/ceshi/webhooks/3/hooktasks.json') -
HTTP 请求
+HTTP 请求
-
GET /api/v1/:owner/:repo/webhooks/:id/hooktasks.json
请求参数:
+请求参数:
-
参数 @@ -11162,7 +11422,7 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/3/hooktasks.jsonwebhook ID 返回字段说明:
+返回字段说明:
参数 @@ -11399,9 +11659,9 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/3/hooktasks.jsoncurl -X POST \ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/3/tests.json
await octokit.request('POST /api/v1/yystopf/ceshi/webhooks/3/tests.json') -
HTTP 请求
+HTTP 请求
-
POST /api/v1/:owner/:repo/webhooks/:id/tests.json
请求参数:
+请求参数:
-
参数 @@ -11433,7 +11693,7 @@ http://localhost:3000/api/v1/yystopf/ceshi/webhooks/3/tests.jsonwebhook ID 返回字段说明:
+返回字段说明:
返回的JSON示例: