Jira API的踩坑記


我本來是想寫篇文章,吐槽一下jira的api的,但是發現最終jira api,很多地方又讓我學到了一些新知識。有些方面真的是沒見過這么標准使用的。可能是我之前孤陋寡聞啦,所以本文的內容不僅僅是講jira的坑, 還有一些是jira本身優良的品性,不僅讓我學到了一些知識,也讓我對規范有了新的理解。 本文的內容算是對我最近這段時間以來對接jira API的經驗總結,希望能對各位有所幫助。

沒有中文

這個是對我來說最大的困難,本身我的英文水平不好之前,閱讀文檔或者說是一些文章都是直接一鍵翻譯,但是碰到jira的API文檔就有點蒙逼了。 本來我以為在國內有很多公司都在用jira, 這里面少不了的API接口進行功能性的封裝,肯定會有中文的文檔結果經過幾次嘗試搜索之后,我終於確認jira API是沒有中文文檔的。

我使用的一鍵翻譯軟件是瀏覽器自帶的嘗試過一些,他們總是會把接口請求路徑中的英文單詞也翻譯成漢字,這簡直就是不能看。 雖然如此,我還是需要中文翻譯和英文原文對照着看,因為有些地方翻譯成中文之后語序不是那么通順。總體來說,沒有中文文檔對App的接入還是有挺大影響的,因為需要不斷地去對照着英文原文和理解翻譯之后的結果。

在我搜索中文文檔的過程中,我看到網上有很多人對API的實現進行了分享,對我來說還是有點大幫助的。 但內容比較少,僅限於兩三個特別常用的API。 沒有人完整的翻譯過jira API的文檔,然后我發現了一個巨坑的事情: jira App文檔分嗯多個版本,基本上每一個版本的基拉就對應一個版本的API文檔,我沒有仔細去看這里面的區別,但是我覺得一個版本一個文檔,着實有點坑了。 大家如果有機會對接jira API文檔,到時候一定要首先確認jira的版本。

HTTPcode

在jira API文檔中,http協議響應狀態碼有很多使用。在我之前的工作經歷中,很少注意到http響應狀態碼這個數據。 因為大多數情況都是成功的話,返回200,不成功的話也是返回200(通過業務狀態碼來區分不同原因), 只有在接口請求失敗,或者說服務器故障的時候會處理一下400和500系列的響應狀態碼。 在對接Jira API文檔的過程中,我遇到了很多種之前沒有接觸過的200系列的http協議響應狀態碼。Jira API 是通過http,響應狀態碼來表示業務處理狀態,他並沒有使用業務狀態碼。 所以,在對接的過程中,需要單獨處理每個接口的http響應狀態碼。

在POST和PUT全球方法的接口, 很少能看到200的狀態響應碼。 下面分享一下,我常見到的201和204狀態響應碼的標准規范。

201 Created

請求已經被實現,而且有一個新的資源已經依據請求的需要而建立,且其 URI 已經隨Location 頭信息返回。假如需要的資源無法及時建立的話,應當返回 '202 Accepted'。

204 No Content

服務器成功處理了請求,但不需要返回任何實體內容,並且希望返回更新了的元信息。響應可能通過實體頭部的形式,返回新的或更新后的元信息。如果存在這些頭部信息,則應當與所請求的變量相呼應。如果客戶端是瀏覽器的話,那么用戶瀏覽器應保留發送了該請求的頁面,而不產生任何文檔視圖上的變化,即使按照規范新的或更新后的元信息應當被應用到用戶瀏覽器活動視圖中的文檔。由於204響應被禁止包含任何消息體,因此它始終以消息頭后的第一個空行結尾。

響應不統一

在之前文章一起吐槽接口文檔中, 我吐槽了一下,接口文檔最坑的就是響應不統一,沒想到在對接Jira文檔的時候就出現了特別多這樣的實踐機會。我曾經一度懷疑Jira文檔是不是故意這么做的,因為各個接口的響應結果。均為json形式,但是最外層的json響應結構。有點1000個接口有1000個響應的即視感。

我之前寫項目測試框架的時候,都會對響應結果進行統一的json格式處理,但是對於Jira的api就沒有辦法使用統一的格式處理,每一個接口都需要進行單獨的處理。這無疑也增加了工作量。下面分享我遇到的幾種響應結構。

{
    "issues": [
        {
            "id": "10000",
            "key": "TST-24",
            "self": "http://www.example.com/jira/rest/api/2/issue/10000"
        },
        {
            "id": "10001",
            "key": "TST-25",
            "self": "http://www.example.com/jira/rest/api/2/issue/10001"
        }
    ],
    "errors": []
}
{
    "id": "10000",
    "key": "TST-24",
    "self": "http://www.example.com/jira/rest/api/2/issue/10000"
}

下面這種就屬於比較霸王級別的難看。一個響應結構體去。表示編輯之后的issues的狀態。結果沒想到在JSON對象中包了這么多層。為了讓文章能縮短一下,我把里數組重復的內容給刪除了,但是還是有這么復雜的響應結構體,簡直就是喪心病狂!

{
    "id": "https://docs.atlassian.com/jira/REST/schema/issue-update#",
    "title": "Issue Update",
    "type": "object",
    "properties": {
        "transition": {
            "title": "Transition",
            "type": "object",
            "properties": {
                "id": {
                    "type": "string"
                },
                "name": {
                    "type": "string"
                },
                "to": {
                    "title": "Status",
                    "type": "object",
                    "properties": {
                        "statusColor": {
                            "type": "string"
                        },
                        "description": {
                            "type": "string"
                        },
                        "iconUrl": {
                            "type": "string"
                        },
                        "name": {
                            "type": "string"
                        },
                        "id": {
                            "type": "string"
                        },
                        "statusCategory": {
                            "title": "Status Category",
                            "type": "object",
                            "properties": {
                                "id": {
                                    "type": "integer"
                                },
                                "key": {
                                    "type": "string"
                                },
                                "colorName": {
                                    "type": "string"
                                },
                                "name": {
                                    "type": "string"
                                }
                            },
                            "additionalProperties": false
                        }
                    },
                    "additionalProperties": false
                },
                "fields": {
                    "type": "object",
                    "patternProperties": {
                        ".+": {
                            "title": "Field Meta",
                            "type": "object",
                            "properties": {
                                "required": {
                                    "type": "boolean"
                                },
                                "schema": {
                                    "title": "Json Type",
                                    "type": "object",
                                    "properties": {
                                        "type": {
                                            "type": "string"
                                        },
                                        "items": {
                                            "type": "string"
                                        },
                                        "system": {
                                            "type": "string"
                                        },
                                        "custom": {
                                            "type": "string"
                                        },
                                        "customId": {
                                            "type": "integer"
                                        }
                                    },
                                    "additionalProperties": false
                                },
                                "name": {
                                    "type": "string"
                                },
                                "autoCompleteUrl": {
                                    "type": "string"
                                },
                                "hasDefaultValue": {
                                    "type": "boolean"
                                },
                                "operations": {
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    }
                                },
                                "allowedValues": {
                                    "type": "array",
                                    "items": {}
                                }
                            },
                            "additionalProperties": false,
                            "required": [
                                "required"
                            ]
                        }
                    },
                    "additionalProperties": false
                }
            },
            "additionalProperties": false
        },
        "fields": {
            "type": "object",
            "patternProperties": {
                ".+": {}
            },
            "additionalProperties": false
        },
        "update": {
            "type": "object",
            "patternProperties": {
                ".+": {
                    "type": "array",
                    "items": {
                        "title": "Field Operation",
                        "type": "object"
                    }
                }
            },
            "additionalProperties": false
        },
        "historyMetadata": {
            "title": "History Metadata",
            "type": "object",
            "properties": {
                "type": {
                    "type": "string"
                },
                "description": {
                    "type": "string"
                },
                "descriptionKey": {
                    "type": "string"
                },
                "activityDescription": {
                    "type": "string"
                },
                "activityDescriptionKey": {
                    "type": "string"
                },
                "emailDescription": {
                    "type": "string"
                },
                "emailDescriptionKey": {
                    "type": "string"
                },
                "actor": {
                    "$ref": "#/definitions/history-metadata-participant"
                },
                "generator": {
                    "$ref": "#/definitions/history-metadata-participant"
                },
                "cause": {
                    "$ref": "#/definitions/history-metadata-participant"
                },
                "extraData": {
                    "type": "object",
                    "patternProperties": {
                        ".+": {
                            "type": "string"
                        }
                    },
                    "additionalProperties": false
                }
            },
            "additionalProperties": false
        },
        "properties": {
            "type": "array",
            "items": {
                "title": "Entity Property",
                "type": "object",
                "properties": {
                    "key": {
                        "type": "string"
                    },
                    "value": {}
                },
                "additionalProperties": false
            }
        }
    },
    "definitions": {
        "history-metadata-participant": {
            "title": "History Metadata Participant",
            "type": "object",
            "properties": {
                "id": {
                    "type": "string"
                },
                "displayName": {
                    "type": "string"
                },
                "displayNameKey": {
                    "type": "string"
                },
                "type": {
                    "type": "string"
                },
                "avatarUrl": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                }
            },
            "additionalProperties": false
        }
    },
    "additionalProperties": false
}

包裝過度

在通常所用到的接口文檔中的接口參數,一般都是通過key value形式去傳參。比較復雜的,可能會用到數組。但是在對接Jira文檔的時候,我發現完全不能以之前的思維慣性去理解Jira API文檔中的接口參數傳遞方式。如果說通常接口參數通過JSON包裝一層的話,那么Jira文檔的接口參數就是里三層外三層。下面我通過幾個實例給大家真實的再現一下雞爪文檔中接口參數的復雜性。

{
    "update": {
        "worklog": [
            {
                "add": {
                    "timeSpent": "60m",
                    "started": "2011-07-05T11:05:00.000+0000"
                }
            }
        ]
    },
    "fields": {
        "project": {
            "id": "10000"
        },
        "summary": "something's wrong",
        "issuetype": {
            "id": "10000"
        },
        "assignee": {
            "name": "homer"
        },
        "reporter": {
            "name": "smithers"
        },
        "priority": {
            "id": "20000"
        },
        "labels": [
            "bugfix",
            "blitz_test"
        ],
        "timetracking": {
            "originalEstimate": "10",
            "remainingEstimate": "5"
        },
        "security": {
            "id": "10000"
        },
        "versions": [
            {
                "id": "10000"
            }
        ],
        "environment": "environment",
        "description": "description",
        "duedate": "2011-03-11",
        "fixVersions": [
            {
                "id": "10001"
            }
        ],
        "components": [
            {
                "id": "10000"
            }
        ],
        "customfield_30000": [
            "10000",
            "10002"
        ],
        "customfield_80000": {
            "value": "red"
        },
        "customfield_20000": "06/Jul/11 3:25 PM",
        "customfield_40000": "this is a text field",
        "customfield_70000": [
            "jira-administrators",
            "jira-software-users"
        ],
        "customfield_60000": "jira-software-users",
        "customfield_50000": "this is a text area. big text.",
        "customfield_10000": "09/Jun/81"
    }
}
{
    "startAt": 0,
    "maxResults": 1,
    "total": 1,
    "comments": [
        {
            "self": "http://www.example.com/jira/rest/api/2/issue/10010/comment/10000",
            "id": "10000",
            "author": {
                "self": "http://www.example.com/jira/rest/api/2/user?username=fred",
                "name": "fred",
                "displayName": "Fred F. User",
                "active": false
            },
            "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.",
            "updateAuthor": {
                "self": "http://www.example.com/jira/rest/api/2/user?username=fred",
                "name": "fred",
                "displayName": "Fred F. User",
                "active": false
            },
            "created": "2016-09-27T09:43:02.795+0000",
            "updated": "2016-09-27T09:43:02.795+0000",
            "visibility": {
                "type": "role",
                "value": "Administrators"
            }
        }
    ]
}

以上兩個是我用遇到的比較麻煩的兩個接口的傳參方式,大家看起來可能會比較復雜。也有點兒迷惑,但其實這兩個接口都不算。最復雜的,因為他案例中這些參數的值大部分是可以不傳的。Jira API文檔中 最讓我感覺到不爽的,還不是這種里山城外三成的包裝方式, 而是同一個參數,可能會出現在多個包裝結構中。 而且這些包裝結構的作用范圍並沒有在文檔中標識出來,導致我想去查一個參數,並不知道兩個地方現在多個地方到底哪個地方有用只能去一各一各的嘗試,雖然對接文檔的工作已經完成了,但是對於文檔中所標記的參數以及傳參格式部分字段依然稀里糊塗。反正功能已經實現啦,就先不去管它啦。

Demo錯誤

接口文檔中最難以讓人忍受的就是接口文檔中存在着硬性錯誤。本來對接接口文檔已經是一個比較麻煩的事情了。如果文檔中出現一些硬性的錯誤。會讓我付出更多的時間和精力去糾正這些錯誤,如果再碰到非常復雜的包裝格式,就更讓人惱火了。回到剛才提到過的Jira api文檔,有非常多個版本,如果文檔出現錯誤,修復起來肯定也是比較多的。我一度認為他這個文檔就是通過工具直接生成的。跟源碼中的文檔標記很相似。

下面分享一條文檔中的錯誤,這是一個接口傳參格式的Demo。乍一看其實沒什么問題,但是這其實並不是JSON的標准格式。在我們閱讀文檔的時候首先就,首先就是要解析出這個中接口傳參格式的JSON展示,我們才能知道具體在JSON好在傳參的時候,在哪一層去傳什么樣的參數。

{"update":{"summary":[{"set":"Bug in business logic"}],"components":[{"set":""}],"timetracking":[{"edit":{"originalEstimate":"1w 1d","remainingEstimate":"4d"}}],"labels":[{"add":"triaged"},{"remove":"blocker"}]},"fields":{"summary":"This is a shorthand for a set operation on the summary field","customfield_10010":1,"customfield_10000":"This is a shorthand for a set operation on a text custom field"},"historyMetadata":{"type":"myplugin:type","description":"text description","descriptionKey":"plugin.changereason.i18.key","activityDescription":"text description","activityDescriptionKey":"plugin.activity.i18.key","actor":{"id":"tony","displayName":"Tony","type":"mysystem-user","avatarUrl":"http://mysystem/avatar/tony.jpg","url":"http://mysystem/users/tony"},"generator":{"id":"mysystem-1","type":"mysystem-application"},"cause":{"id":"myevent","type":"mysystem-event"},"extraData":{"keyvalue":"extra data","goes":"here"}},"properties":[{"key":"key1","value":'properties' : 'can be set at issue create or update time'},{"key":"key2","value":'and' : 'there can be multiple properties'}]}

其實問題出現在最后一個"properties":[{"key":"key1","value":'properties' : 'can be set at issue create or update time'},{"key":"key2","value":'and' : 'there can be multiple properties'}],后面的can be其實是對這個參數的描述,不知道怎么到了接口的傳參Demo里面了。着實讓人頭大。

中英混排

Jira的api文檔都是英文版的,我一度懷疑他並不重視中國區用戶,但是當我看到某些接口的字段值的時候,我一下子震驚了,原來他的字段值還是中英混排的。一下子有點兒不知所措。如圖所示:

jira上issue狀態

issue狀態中居然有中文。真是讓我感動的痛哭涕零,這都什么玩意兒。

POST PUT

在我之前的工作當中,主要接觸的還是get和post接口。對於其他HTTP請求方式並不十分了解,也不太清楚這其中的規范。一直以來的概念就是獲取數據用get,修改數據用post。但是在接觸Jira API文檔的過程中,我仔細地看了看post和PUT的區別。總結如下:

  • 創建用POST,修改PUT
  • POST非冪等,PUT冪等

在傳參格式上,post和put都一樣。設置請求entity的方式也是一樣的,在Java代碼中是通用的,原因在於org.apache.http.client.methods.HttpPutorg.apache.http.client.methods.HttpPost都繼承於抽象類org.apache.http.client.methods.HttpEntityEnclosingRequestBase。這個小知識點我又重新學習了一遍。

Have Fun ~ Tester !


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM