写点什么

Jira API 的踩坑记

用户头像
FunTester
关注
发布于: 2021 年 09 月 10 日

我本来是想写篇文章,吐槽一下 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 文档都是英文版的,我一度怀疑他并不重视中国区用户,但是当我看到某些接口的字段值的时候,我一下子震惊了,原来他的字段值还是中英混排的。一下子有点儿不知所措。如图所示:



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 !

发布于: 2021 年 09 月 10 日阅读数: 8
用户头像

FunTester

关注

公众号:FunTester,650+原创,欢迎关注 2020.10.20 加入

Have Fun,Tester! 公众号FunTester,坚持原创文章的测试人。 FunTester测试框架作者,DCS_FunTester分布式性能测试框架作者。

评论

发布
暂无评论
Jira API的踩坑记