最近在搞自動化監控,有一項功能就是監測到異常情況時自動創建jira工單,於是乎對JIRA REST API 做了一些調研,其提供兩種使用方式,一種是在項目中引入客戶端封裝包jira-rest-java-client-2.0.0-m2.jar,另一種是直接使用JIRA REST API 提供的curl命令行方式處理。
參考資料:https://docs.atlassian.com/jira/REST/7.0-SNAPSHOT
一。第一種方式jira-rest-java-client-2.0.0-m2.jar:
<repositories>
<repository>
<id>central</id>
<name>Atlassian Public Repository</name>
<layout>default</layout>
<url>https://maven.atlassian.com/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependency> <groupId>com.atlassian.jira</groupId> <artifactId>jira-rest-java-client</artifactId> <version>2.0.0-m2</version> </dependency>
這種方式參考資料不好找,我基本是通過查看api一點點摸索的,而且需要引入許多第三方依賴包,相對第二種方式過於繁瑣,這裏只做了常用的方法封裝,相關jar包見附件:
package com.nq.common;
import java.net.URI;
import java.net.URISyntaxException;
import com.atlassian.jira.rest.client.JiraRestClient;
import com.atlassian.jira.rest.client.JiraRestClientFactory;
import com.atlassian.jira.rest.client.domain.BasicIssue;
import com.atlassian.jira.rest.client.domain.Issue;
import com.atlassian.jira.rest.client.domain.IssueType;
import com.atlassian.jira.rest.client.domain.SearchResult;
import com.atlassian.jira.rest.client.domain.Transition;
import com.atlassian.jira.rest.client.domain.User;
import com.atlassian.jira.rest.client.domain.input.IssueInput;
import com.atlassian.jira.rest.client.domain.input.IssueInputBuilder;
import com.atlassian.jira.rest.client.domain.input.TransitionInput;
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
/**
* jira java工具類
* jira-rest-java-client-2.0.0-m2.jar
* @author hanqunfeng
*
*/
public class JiraUtil {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
// JiraUtil.getIssue("NQCP-35");
// JiraUtil.createIssue("NQCP", 1l, "工單詳細信息<br>工單詳細信息",
// "工單主題", "username");
// JiraUtil.printAllIssueType();
// JiraUtil.changeIssueStatus("NQCP-35", 2);
// JiraUtil.getUser("username");
//查詢用戶負責的所有工單
JiraUtil.searchIssues("assignee=username");
System.out.println("*****************************");
JiraUtil.searchIssues("assignee=username order by duedate");
System.out.println("*****************************");
JiraUtil.searchIssues("assignee=username order by issueType");
System.out.println("*****************************");
//從0開始數,從第5條開始,取兩條
// JiraUtil.searchIssues("assignee=username",5,2);
//
// System.out.println("*****************************");
// JiraUtil.searchIssues("project=NQCP");
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
static JiraRestClientFactory factory = new AsynchronousJiraRestClientFactory();
static String uri = "http://jira.local.com/jira";
static String user = "username";
static String pwd = "password";
static JiraRestClient restClient;
/**
* 獲得jira的客戶端
*
* @return JiraRestClient
* @throws URISyntaxException
*/
static JiraRestClient getJiraRestClient() throws URISyntaxException {
if (restClient == null) {
URI jiraServerUri = new URI(uri);
restClient = factory.createWithBasicHttpAuthentication(
jiraServerUri, user, pwd);
}
return restClient;
}
/**
* 獲得工單信息
*
* @param issueKey
* 工單key,比如:NQCP-5
* @throws URISyntaxException
*/
public static Issue getIssue(String issueKey) throws URISyntaxException {
Issue issue = null;
JiraRestClient restClient = getJiraRestClient();
// get issue through issueKey
try {
issue = restClient.getIssueClient().getIssue(issueKey).claim();
// 打印工單後續的工作流
Iterable<Transition> iter = restClient.getIssueClient()
.getTransitions(issue).claim();
for (Transition transition : iter) {
System.out.println(transition);
}
} catch (Exception e) {
e.printStackTrace();
}
// 打印工單明細
System.out.println(issue);
return issue;
}
/**
* 檢索工單
* @param jql
*
* @return
* @throws URISyntaxException
*/
public static Iterable<BasicIssue> searchIssues(String jql) throws URISyntaxException{
JiraRestClient restClient = getJiraRestClient();
SearchResult searchResutl = restClient.getSearchClient().searchJql(jql).claim();
Iterable<BasicIssue> iter = searchResutl.getIssues();
for (BasicIssue baseIssue : iter) {
System.out.println(baseIssue);
}
return iter;
}
/**
* 檢索工單
* @param jql
* @param startIndex
* @param maxResults
* @return
* @throws URISyntaxException
*/
public static Iterable<BasicIssue> searchIssues(String jql,int startIndex, int maxResults) throws URISyntaxException{
JiraRestClient restClient = getJiraRestClient();
SearchResult searchResutl = restClient.getSearchClient().searchJql(jql,maxResults,startIndex).claim();
Iterable<BasicIssue> iter = searchResutl.getIssues();
for (BasicIssue baseIssue : iter) {
System.out.println(baseIssue);
}
return iter;
}
/**
* 打印jira系統中已經創建的全部issueType
* issuetype/
*
* @throws URISyntaxException
*/
public static Iterable<IssueType> printAllIssueType() throws URISyntaxException {
JiraRestClient restClient = getJiraRestClient();
Iterable<IssueType> iter = restClient.getMetadataClient()
.getIssueTypes().claim();
for (IssueType issueType : iter) {
System.out.println(issueType);
}
return iter;
}
/**
* 創建一個新工單
*
* @param projectKey
* 項目key,比如:NQCP
* @param issueType
* 工單類型,來源於printAllIssueType()的id
* @param description
* 工單描述
* @param summary
* 工單主題
* @param assignee
* 工單負責人
* @throws URISyntaxException
*/
public static BasicIssue createIssue(String projectKey, Long issueType,
String description, String summary, String assignee)
throws URISyntaxException {
JiraRestClient restClient = getJiraRestClient();
IssueInputBuilder issueBuilder = new IssueInputBuilder(projectKey,
issueType);
issueBuilder.setDescription(description);
issueBuilder.setSummary(summary);
if (getUser(assignee) != null) {
issueBuilder.setAssigneeName(assignee);
}
IssueInput issueInput = issueBuilder.build();
BasicIssue bIssue = null;
try {
bIssue = restClient.getIssueClient().createIssue(issueInput)
.claim();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(bIssue);
return bIssue;
}
/**
* 獲取用戶信息
*
* @param username
* @return
* @throws URISyntaxException
*/
public static User getUser(String username) throws URISyntaxException {
JiraRestClient restClient = getJiraRestClient();
User user = null;
try {
user = restClient.getUserClient().getUser(username).claim();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(user);
return user;
}
/**
* 改變工單workflow狀態 issue的workflow是不可以隨便改變的,必須按照流程圖的順序進行改變,具體如下:
*
* 當前狀態 :說明 變更流程id:說明 >> 變更後狀態
1:open,開放 1)4:start progress >> in progerss 2)5:resolve issue >> resolved 3)2:close issue >> closed
3:in progerss 正在處理 1)301:stop progress >> open 2)5:resolve issue >> resolved 3)2:close issue >> closed
4:resolved 已解決 1)701:close issue >> closed 2)3:reopen issue >> reopened
5:reopened 重新打開 1)4:start progress >> in progerss 2)5:resolve issue >> resolved 3)2:close issue >> closed
6:closed 已關閉 1)3:reopen issue >> reopened
*
*
* 可通過如下方式查看當前工單的後續工作流程: Iterable<Transition> iter =
* restClient.getIssueClient().getTransitions(issue).claim();
*
* for (Transition transition : iter) { System.out.println(transition); }
*
* 輸出結果:當前工單狀態是 5:reopened 重新打開 Transition{id=4, name=Start Progress,
* fields=[]} Transition{id=5, name=Resolve Issue,
* fields=[Field{id=fixVersions, isRequired=false, type=array},
* Field{id=resolution, isRequired=true, type=resolution}]} Transition{id=2,
* name=Close Issue, fields=[Field{id=fixVersions, isRequired=false,
* type=array}, Field{id=resolution, isRequired=true, type=resolution}]}
*
*
* @param issuekey
* 工單key
* @param statusId
* 變更流程id
* @param fields
* 隨狀態需要傳遞的參數,可以爲空
* @throws URISyntaxException
*/
public static void changeIssueStatus(String issuekey, int statusId)
throws URISyntaxException {
JiraRestClient restClient = getJiraRestClient();
Issue issue = getIssue(issuekey);
if (issue != null) {
TransitionInput tinput = new TransitionInput(statusId);
restClient.getIssueClient().transition(issue, tinput);
}
// try {
// Thread.sleep(3000);//因爲是異步處理,所以狀態是延遲變更的,暫停一下可以看到狀態已經變更了
// issue = getIssue(issuekey);
// System.out.println(issue.getStatus());
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
}
二。JIRA REST API
這種方式使用curl命令進行處理,幾乎涵蓋了所有的jira處理,這裏也只對常用的功能進行的封裝。
接口中的 2 可以使用 latest 替代。http://localhost:9080/rest/api/latest/issue/ORCM-1 《==》 http://localhost:9080/rest/api/2/issue/ORCM-1
package com.nq.common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
/**
* JIRA REST API 工具類
* https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials
* https://docs.atlassian.com/jira/REST/7.0-SNAPSHOT/
* @author hanqunfeng
*
*/
public class JiraAPIUtil {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// JiraAPIUtil.getIssue("NQCP-35");
// Map<String, String> map = new HashMap<String, String>();
// map.put("assignee", "{\"name\":\"username\"}");
// JiraAPIUtil.createIssue("NQCP", "Bug", "哈哈哈啊哈哈", "測試01", map);
// Map<String,String> map = new HashMap<String,String>();
// map.put("assignee", "{\"name\":\"username\"}");
// map.put("summary", "\"summary00002\"");
// JiraAPIUtil.editIssue("NQCP-38", map);
// JiraAPIUtil.searchIssues("assignee=username");
// System.out.println("*****************************");
// JiraAPIUtil.searchIssues("assignee=username+order+by+duedate");
// System.out.println("*****************************");
// JiraAPIUtil.addComments("NQCP-39", "123456哈哈哈哈");
// JiraAPIUtil.deleteIssueByKey("NQCP-38");
JiraAPIUtil.addAttachment("NQCP-39", "d://myfile01.txt"); //linux路徑: /home/boss/myfile.txt
}
static String uri = "http://jira.local.com/jira";
static String user = "username";
static String pwd = "password";
static String osname = System.getProperty("os.name").toLowerCase();
/**
* 執行shell腳本
*
* @param command
* @return
* @throws IOException
*/
private static String executeShell(String command) throws IOException {
StringBuffer result = new StringBuffer();
Process process = null;
InputStream is = null;
BufferedReader br = null;
String line = null;
try {
if (osname.indexOf("windows") >= 0) {
process = new ProcessBuilder("cmd.exe", "/c", command).start();
System.out.println("cmd.exe /c " + command); //安裝Cygwin,使windows可以執行linux命令
} else {
process = new ProcessBuilder("/bin/sh", "-c", command).start();
System.out.println("/bin/sh -c " + command);
}
is = process.getInputStream();
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
while ((line = br.readLine()) != null) {
System.out.println(line);
result.append(line);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
br.close();
process.destroy();
is.close();
}
return result.toString();
}
/**
* 活動工單信息
*
* @param issueKey
* 工單key
* @return
* @throws IOException
*/
public static String getIssue(String issueKey) throws IOException {
String command = "curl -D- -u " + user + ":" + pwd
+ " -X GET -H \"Content-Type: application/json\" \"" + uri
+ "/rest/api/2/issue/" + issueKey + "\"";
String issueSt = executeShell(command);
return issueSt;
}
/**
* 創建工單
*
* @param projectKey
* 項目key
* @param issueType
* 工單類型 name
* @param description
* 工單描述
* @param summary
* 工單主題
* @param assignee
* 工單負責人
* @param map
* 工單參數map,key爲參數名稱,value爲參數值,參數值必須自帶雙引號 比如: map.put("assignee",
* "{\"name\":\"username\"}"); map.put("summary",
* "\"summary00002\"");
* @return
* @throws IOException
*/
public static String createIssue(String projectKey, String issueType,
String description, String summary,
Map<String, String> map) throws IOException {
String fields = "";
if (map != null && map.size() > 0) {
StringBuffer fieldsB = new StringBuffer();
for (Map.Entry<String, String> entry : map.entrySet()) {
fieldsB.append(",\"").append(entry.getKey()).append("\":")
.append(entry.getValue());
}
fields = fieldsB.toString();
}
String command = "curl -D- -u " + user + ":" + pwd
+ " -X POST --data '{\"fields\": {\"project\":{ \"key\": \""
+ projectKey + "\"},\"summary\": \"" + summary
+ "\",\"description\": \"" + description
+ "\",\"issuetype\": {\"name\": \"" + issueType + "\"}"
+ fields + "}}' -H \"Content-Type: application/json\" \"" + uri
+ "/rest/api/2/issue/\"";
String issueSt = executeShell(command);
return issueSt;
}
/**
* 更新工單
*
* @param issueKey
* 工單key
* @param map
* 工單參數map,key爲參數名稱,value爲參數值,參數值必須自帶雙引號 比如: map.put("assignee",
* "{\"name\":\"username\"}"); map.put("summary",
* "\"summary00002\"");
* @return
* @throws IOException
*/
public static String editIssue(String issueKey, Map<String, String> map)
throws IOException {
StringBuffer fieldsB = new StringBuffer();
for (Map.Entry<String, String> entry : map.entrySet()) {
fieldsB.append("\"").append(entry.getKey()).append("\":")
.append(entry.getValue()).append(",");
}
String fields = fieldsB.toString();
fields = fields.substring(0, fields.length() - 1);
String command = "curl -D- -u " + user + ":" + pwd
+ " -X PUT --data '{\"fields\": { " + fields
+ "}}' -H \"Content-Type: application/json\" \"" + uri
+ "/rest/api/2/issue/" + issueKey + "\"";
String issueSt = executeShell(command);
return issueSt;
}
/**
* 查詢工單
* @param jql
* assignee=username
* assignee=username&startAt=2&maxResults=2
* assignee=username+order+by+duedate
* project=projectKey+order+by+duedate&fields=id,key
* @return
* @throws IOException
*/
public static String searchIssues(String jql) throws IOException{
String command = "curl -D- -u " + user + ":" + pwd
+ " -X GET -H \"Content-Type: application/json\" \"" + uri
+ "/rest/api/2/search?jql=" + jql + "\"";
String issueSt = executeShell(command);
return issueSt;
}
/**
* 爲工單增加註釋說明
* @param issueKey 工單key
* @param comment 註釋說明
* @return
* @throws IOException
*/
public static String addComments(String issueKey,String comments) throws IOException{
String command = "curl -D- -u " + user + ":" + pwd
+ " -X PUT --data '{\"update\": { \"comment\": [ { \"add\": { \"body\":\""+comments+"\" } } ] }}' -H \"Content-Type: application/json\" \"" + uri
+ "/rest/api/2/issue/" + issueKey + "\"";
String issueSt = executeShell(command);
return issueSt;
}
/**
* 刪除工單
* @param issueKey 工單key
* @return
* @throws IOException
*/
public static String deleteIssueByKey(String issueKey) throws IOException{
String command = "curl -D- -u " + user + ":" + pwd
+ " -X DELETE -H \"Content-Type: application/json\" \"" + uri
+ "/rest/api/2/issue/" + issueKey + "\"";
String issueSt = executeShell(command);
return issueSt;
}
/**
* 上傳附件
* @param issueKey 工單key
* @param filepath 文件路徑
* @return
* @throws IOException
*/
public static String addAttachment(String issueKey,String filepath) throws IOException{
String command = "curl -D- -u " + user + ":" + pwd
+ " -X POST -H \"X-Atlassian-Token: nocheck\" -F \"file=@"+filepath+"\" \"" + uri
+ "/rest/api/2/issue/" + issueKey + "/attachments\"";
String issueSt = executeShell(command);
return issueSt;
}
}